/*global jQuery:false*/

/**
 * enscroll.js - jQuery plugin to add custom scrollbars to HTML block elements
 * Copyright (C) 2012 Jason T. Stoudt
 * Released under the MIT license
 * http://enscrollplugin.com/license.html
 **/


/**
* Updated many times by: Mohammad AlBanna
* Website: MBanna.me
**/

//If the file has injected many times
if($(".jsContainer").length >= 1){
    throw new Error("Injected!");
}


;(function( $, win, doc, undefined ) {

	var defaultSettings = {
		verticalScrolling: true,
		horizontalScrolling: false,
        verticalScrollerSide: 'right',
		showOnHover: false,
		scrollIncrement: 20,
		minScrollbarLength: 40,
		pollChanges: true,
		drawCorner: true,
		drawScrollButtons: false,
		clickTrackToScroll: true,
		easingDuration: 500,
		propagateWheelEvent: true,
		verticalTrackClass: 'vertical-track',
		horizontalTrackClass: 'horizontal-track',
		horizontalHandleClass: 'horizontal-handle',
		verticalHandleClass: 'vertical-handle',
		scrollUpButtonClass: 'scroll-up-btn',
		scrollDownButtonClass: 'scroll-down-btn',
		scrollLeftButtonClass: 'scroll-left-btn',
		scrollRightButtonClass: 'scroll-right-btn',
		cornerClass: 'scrollbar-corner',
		zIndex: 1,
		addPaddingToPane: true,
		horizontalHandleHTML: '<div class="left"></div><div class="right"></div>',
		verticalHandleHTML: '<div class="top"></div><div class="bottom"></div>'
	},

	preventDefault = function( event ) {
		if ( event.preventDefault ) {
			event.preventDefault();
		} else {
			event.returnValue = false;
		}

		if ( event.stopPropagation ) {
			event.stopPropagation();
		} else {
			event.cancelBubble = true;
		}
	},

	// normalize requestAnimationFrame function and polyfill if needed
	reqAnimFrame = win.requestAnimationFrame ||
			win.mozRequestAnimationFrame ||
			win.webkitRequestAnimationFrame ||
			win.oRequestAnimationFrame ||
			win.msRequestAnimationFrame ||
			function( f ) { setTimeout( f, 17 ); },

	getComputedValue = function( elem, property ) {
		var w = $( elem ).css( property ),
			matches = /^-?\d+/.exec( w );
		return matches ? +matches[0] : 0;
	},

	testScrollHeight = function( nodeName ) {
		var styles = {
				width: '5px',
				height: '1px',
				overflow: 'hidden',
				padding: '8px 0',
				visibility: 'hidden',
				whiteSpace: 'pre-line',
				font: '10px/1 serif'
			},
			pane = document.createElement( nodeName ),
			textNode = document.createTextNode( 'a\na' ),
			result, attr;

		for ( attr in styles ) {
			pane.style[ attr ] = styles[ attr ];
		}

		pane.appendChild( textNode );
		document.body.appendChild( pane );

		result = ( pane.scrollHeight < 28 );

		document.body.removeChild( pane );

		return result;
	},

	PI_OVER_2 = 0.5 * Math.PI,

	TEN_LOG2 = 10 * Math.log( 2 ),

	easeOutSin = function( c, d, t ) {
		var b = PI_OVER_2 / d,
			a = c * b;

		return Math.round( a * Math.cos( b * t ));
	},

	easeOutExpo = function( c, d, t ) {
		return Math.round( c * TEN_LOG2 * Math.pow( 2, -10 * t / d + 1 ) / d );
	},

	timeFromPosition = function( b, c, d, x ) {
		return 2 * d / Math.PI * Math.asin(( x - b ) / c );
	},

	showScrollbars = function( scheduleHide ) {

		var data = $( this ).data( 'enscroll' ),
			that = this,
			settings = data.settings,
			hideScrollbars = function() {
				var data = $( this ).data( 'enscroll' ),
					settings = data.settings;

				if ( data && settings.showOnHover ) {
					if ( settings.verticalScrolling &&
						$( data.verticalTrackWrapper ).is( ':visible' )) {
						$( data.verticalTrackWrapper ).stop().fadeTo( 275, 0 );
					}

					if ( settings.horizontalScrolling &&
						$( data.horizontalTrackWrapper ).is( ':visible' )) {
						$( data.horizontalTrackWrapper ).stop().fadeTo( 275, 0 );
					}
					data._fadeTimer = null;
				}
			};

		if ( data && settings.showOnHover ) {
			if ( data._fadeTimer ) {
				clearTimeout( data._fadeTimer );
			} else {
				if ( settings.verticalScrolling &&
					$( data.verticalTrackWrapper ).is( ':visible' )) {
					$( data.verticalTrackWrapper ).stop().fadeTo( 275, 1 );
				}

				if ( settings.horizontalScrolling &&
					$( data.horizontalTrackWrapper ).is( ':visible' )) {
					$( data.horizontalTrackWrapper ).stop().fadeTo( 275, 1 );
				}
			}

			if ( scheduleHide !== false ) {
				data._fadeTimer = setTimeout(function() {
					hideScrollbars.call( that );
				}, 1750);
			}
		}
	},

	scrollVertical = function( pane, dy ) {
		var $pane = $( pane ),
			data = $pane.data( 'enscroll' ),
			y0 = $pane.scrollTop();

		if ( data && data.settings.verticalScrolling ) {
			$pane.scrollTop( y0 + dy );
			if ( data.settings.showOnHover ) {
				showScrollbars.call( pane );
			}
		}
	},

	scrollHorizontal = function( pane, dx ) {

		var $pane = $( pane ),
			data = $pane.data( 'enscroll' ),
			x0 = $pane.scrollLeft();
		if ( data && data.settings.horizontalScrolling ) {
			$pane.scrollLeft( x0 + dx );
			if ( data.settings.showOnHover ) {
				showScrollbars.call( pane );
			}
		}
	},

	startVerticalDrag = function( event ) {
		// only handle events for left mouse button dragging
		if ( event.which !== 1 ) {
			return;
		}

		var pane = event.data.pane,
			$pane = $( pane ),
			data = $pane.data( 'enscroll' ),
			dragging = true,
			$track, handle, handleY, oldHandleY, mouseYOffset,
			trackYOffset, bodyCursor, trackDiff, paneDiff,

			moveHandle = function() {
				if ( !dragging ) {
					return;
				}

				if ( handleY !== oldHandleY ) {
					if ( !data._scrollingY ) {
						data._scrollingY = true;
						data._startY = $pane.scrollTop();
						reqAnimFrame( function() {
							scrollAnimate( $pane );
						});
					}

					handle.style.top = handleY + 'px';

					data._endY = handleY * paneDiff / trackDiff;
					oldHandleY = handleY;
				}

				reqAnimFrame( moveHandle );

				if ( data.settings.showOnHover ) {
					showScrollbars.call( pane );
				}
			},

			moveDrag = function( event ) {
				if ( dragging ) {
					handleY = event.clientY - trackYOffset - mouseYOffset;
					handleY = Math.min( handleY < 0 ? 0 : handleY, trackDiff );
				}
				return false;
			},

			endDrag = function() {
				dragging = false;

				doc.body.style.cursor = bodyCursor;
				this.style.cursor = '';
				$track.removeClass( 'dragging' );

				$( doc.body )
					.off( 'mousemove.enscroll.vertical' )
					.off( 'mouseup.enscroll.vertical' );

				$( doc ).off( 'mouseout.enscroll.vertical' );

				$pane.on( 'scroll.enscroll.pane', function( event ) {
					paneScrolled.call( this, event );
				});

				return false;
			};

		$track = $( data.verticalTrackWrapper ).find( '.enscroll-track' );
		handle = $track.children().first()[0];
		handleY = parseInt( handle.style.top, 10 );
		paneDiff = pane.scrollHeight -
			(data._scrollHeightNoPadding ? $(pane).height() : $(pane).innerHeight());

		mouseYOffset = event.clientY - $( handle ).offset().top;
		trackDiff = $track.height() - $( handle ).outerHeight();
		trackYOffset = $track.offset().top;

		$pane.off( 'scroll.enscroll.pane' );

		$( doc.body ).on({
			'mousemove.enscroll.vertical': moveDrag,
			'mouseup.enscroll.vertical': function( event ) {
				endDrag.call( handle, event );
			}
		});

		$( doc ).on( 'mouseout.enscroll.vertical', function( event ) {
			if ( event.target.nodeName && event.target.nodeName.toUpperCase() === 'HTML' ) {
				endDrag.call( handle, event );
			}
		});

		if ( !$track.hasClass( 'dragging' ) ) {
			$track.addClass( 'dragging' );
			bodyCursor = $( doc.body ).css( 'cursor' );
			this.style.cursor = doc.body.style.cursor = 'ns-resize';
		}

		reqAnimFrame( moveHandle );

		return false;
	},

	startHorizontalDrag = function( event ) {
		// dragging the scrollbar handle only works with left mouse button
		if ( event.which !== 1 ) {
			return;
		}

		var pane = event.data.pane,
			$pane = $( pane ),
			data = $( pane ).data( 'enscroll' ),
			dragging = true,
			$track, handle, handleX, oldHandleX, paneDiff,
			mouseXOffset, trackXOffset, bodyCursor, trackDiff,

			moveHandle = function() {
				if ( !dragging ) {
					return;
				}

				if ( handleX !== oldHandleX ) {
					if ( !data._scrollingX ) {
						data._scrollingX = true;
						data._startX = $pane.scrollLeft();
						reqAnimFrame( function() {
							scrollAnimate( $pane );
						});
					}

					handle.style.left = handleX + 'px';

					data._endX = handleX * paneDiff / trackDiff;
					oldHandleX = handleX;
				}

				reqAnimFrame( moveHandle );

				if ( data.settings.showOnHover ) {
					showScrollbars.call( pane );
				}
			},

			moveDrag = function( event ) {
				if ( dragging ) {
					handleX = event.clientX - trackXOffset - mouseXOffset;
					handleX = Math.min( handleX < 0 ? 0 : handleX, trackDiff );
				}
				return false;
			},

			endDrag = function() {
				dragging = false;

				$track.removeClass('dragging');

				doc.body.style.cursor = bodyCursor;
				this.style.cursor = '';
				$track.removeClass( 'dragging' );

				$( doc.body )
					.off( 'mousemove.enscroll.horizontal' )
					.off( 'mouseup.enscroll.horizontal' );

				$( doc ).off ( 'mouseout.enscroll.horizontal' );

				$pane.on( 'scroll.enscroll.pane', function( event ) {
					paneScrolled.call( this, event );
				});

				return false;
			};

		$track = $( data.horizontalTrackWrapper ).find( '.enscroll-track' );
		handle = $track.children().first()[0];
		handleX = parseInt( handle.style.left, 10 );
		paneDiff = pane.scrollWidth - $( pane ).innerWidth();

		mouseXOffset = event.clientX - $( handle ).offset().left;
		trackDiff = $track.width() - $( handle ).outerWidth();
		trackXOffset = $track.offset().left;

		$pane.off( 'scroll.enscroll.pane' );

		$( doc.body ).on({
			'mousemove.enscroll.horizontal': moveDrag,
			'mouseup.enscroll.horizontal': function( event ) {
				endDrag.call( handle, event );
			}
		});

		$( doc ).on( 'mouseout.enscroll.horizontal', function( event ) {
			if ( event.target.nodeName && event.target.nodeName.toUpperCase() === 'HTML' ) {
				endDrag.call( handle, event );
			}
		});

		if ( !$track.hasClass( 'dragging' ) ) {
			$track.addClass( 'dragging' );
			bodyCursor = $( 'body' ).css( 'cursor' );
			this.style.cursor = doc.body.style.cursor = 'ew-resize';
		}

		reqAnimFrame( moveHandle );

		return false;

	},

	scrollAnimate = function( $pane ) {
		var data = $pane.data( 'enscroll' ),
			d = data._duration,
			c, curPos, t;

		if ( data._scrollingX === true ) {
			c = data._endX - data._startX;
			if ( c === 0 ) {
				data._scrollingX = false;
			} else {
				curPos = $pane.scrollLeft();
				t = timeFromPosition( data._startX, c, d, curPos );
				if ( c > 0 ) {
					if ( curPos >= data._endX || curPos < data._startX ) {
						data._scrollingX = false;
					} else {
						scrollHorizontal( $pane,
							Math.max( 1, easeOutSin( c, d, t )));
						reqAnimFrame( function() {
							scrollAnimate( $pane );
						});
					}
				} else {
					if ( curPos <= data._endX || curPos > data._startX ) {
						data._scrollingX = false;
					} else {
						scrollHorizontal( $pane,
							Math.min( -1, easeOutSin( c, d, t )));
						reqAnimFrame( function() {
							scrollAnimate( $pane );
						});
					}
				}
			}
		}

		if ( data._scrollingY === true ) {
			c = data._endY - data._startY;
			if ( c === 0 ) {
				data._scrollingY = false;
			} else {
				curPos = $pane.scrollTop();
				t = timeFromPosition( data._startY, c, d, curPos );
				if ( c > 0 ) {
					if ( curPos >= data._endY || curPos < data._startY ) {
						data._scrollingY = false;
					} else {
						scrollVertical( $pane,
							Math.max( 1, easeOutSin( c, d, t )));
						reqAnimFrame( function() {
							scrollAnimate( $pane );
						});
					}
				} else {
					if ( curPos <= data._endY || curPos > data._startY ) {
						data._scrollingY = false;
					} else {
						scrollVertical( $pane,
							Math.min( -1, easeOutSin( c, d, t )));
						reqAnimFrame( function() {
							scrollAnimate( $pane );
						});
					}
				}
			}
		}

	},

	scrollAnimateHorizontal = function( $pane, delta ) {
		var data = $pane.data( 'enscroll' ),
			curPos = $pane.scrollLeft(),
			scrollMax = $pane[0].scrollWidth - $pane.innerWidth();


		if ( !data.settings.horizontalScrolling || data._scrollingY ) {
			return false;
		}

		if ( !data._scrollingX ) {
			data._scrollingX = true;
			data._startX = curPos;
			data._endX = data._startX;
			reqAnimFrame( function() {
				scrollAnimate( $pane );
			});
		}

		data._endX = delta > 0 ? Math.min( curPos + delta, scrollMax ) :
			Math.max( 0, curPos + delta );

		return delta < 0 && curPos > 0 || delta > 0 && curPos < scrollMax;
	},

	scrollAnimateVertical = function( $pane, delta ) {
		var data = $pane.data( 'enscroll' ),
			curPos = $pane.scrollTop(),
			scrollMax = $pane[0].scrollHeight - (data._scrollHeightNoPadding ? $pane.height() : $pane.innerHeight());

		if ( !data.settings.verticalScrolling || data._scrollingX ) {
			return false;
		}

		if ( !data._scrollingY ) {
			data._scrollingY = true;
			data._startY = curPos;
			data._endY = data._startY;
			reqAnimFrame( function() {
				scrollAnimate( $pane );
			});
		}

		data._endY = delta > 0 ? Math.min( curPos + delta, scrollMax ) :
			Math.max( 0, curPos + delta );

		return delta < 0 && curPos > 0 || delta > 0 && curPos < scrollMax;
	},

	mouseScroll = function( event ) {
		var $pane = $( this ),
			data = $pane.data( 'enscroll' ),
			scrollIncrement = data.settings.scrollIncrement,
			deltaX = 'deltaX' in event ? -event.deltaX :
				'wheelDeltaX' in event ? event.wheelDeltaX :
				0,
			deltaY = 'deltaY' in event ? -event.deltaY :
				'wheelDeltaY' in event ? event.wheelDeltaY :
				'wheelDelta' in event ? event.wheelDelta :
				0,
			delta;

		if ( Math.abs( deltaX ) > Math.abs( deltaY )) {
			delta = ( deltaX > 0 ? -scrollIncrement : scrollIncrement ) << 2;
			if ( scrollAnimateHorizontal( $pane, delta ) || !data.settings.propagateWheelEvent ) {
				preventDefault( event );
			}
		} else {
			delta = ( deltaY > 0 ? -scrollIncrement : scrollIncrement ) << 2;
			if (deltaY != 0 && (scrollAnimateVertical( $pane, delta ) || !data.settings.propagateWheelEvent) ) {
				preventDefault( event );
			}
		}
	},

	paneScrolled = function() {
		var $this = $( this ),
			data = $this.data( 'enscroll' ),
			handle, track, pct;

		if ( data ) {
			if ( data.settings.verticalScrolling ) {
				track = $( data.verticalTrackWrapper ).find( '.enscroll-track' )[0];
				handle = track.firstChild;
				pct = $this.scrollTop() / ( this.scrollHeight - (data._scrollHeightNoPadding ? $this.height() : $this.innerHeight()));
				pct = isNaN( pct ) ? 0 : pct;

				handle.style.top = ( pct * ( $( track ).height() - $( handle ).outerHeight() )) + 'px';
			}

			if ( data.settings.horizontalScrolling ) {
				track = $( data.horizontalTrackWrapper ).find( '.enscroll-track' )[0];
				handle = track.firstChild;
				pct = $this.scrollLeft() / ( this.scrollWidth - $this.innerWidth() );
				pct = isNaN( pct ) ? 0 : pct;

				handle.style.left = ( pct * ( $( track ).width() - $( handle ).innerWidth() )) + 'px';
			}
		}
	},

	keyHandler = function( event ) {
		var $this = $( this ),
			data = $this.data( 'enscroll' ),
			scrollIncrement;

		// dont' have key events if this element is a user-input element
		if (/(input)|(select)|(textarea)/i.test( this.nodeName )) {
			return;
		}

		// don't handle events that have just bubbled up
		if ( event.target === this && data ) {
			scrollIncrement = data.settings.scrollIncrement;

			switch( event.keyCode ) {
				case 32: // space
				case 34: // page down
					scrollAnimateVertical( $this, $this.height() );
					return false;
				case 33: // page up
					scrollAnimateVertical( $this, -$this.height() );
					return false;
				case 35: // end
					scrollAnimateVertical( $this, this.scrollHeight );
					return false;
				case 36: // home
					scrollAnimateVertical( $this, -this.scrollHeight );
					return false;
				case 37: // left
					scrollAnimateHorizontal( $this, -scrollIncrement );
					return false;
				case 38: // up
					scrollAnimateVertical( $this, -scrollIncrement );
					return false;
				case 39: // right
					scrollAnimateHorizontal( $this, scrollIncrement );
					return false;
				case 40: // down
					scrollAnimateVertical( $this, scrollIncrement );
					return false;
			}

			return true;
		}
	},

	dragHandler = function() {
		var pane = this,
			settings = $( pane ).data( 'enscroll' ).settings,
			dragging = true,
			deltaX = 0,
			deltaY = 0,
			paneTop = $( pane ).offset().top,
			paneBottom = paneTop + $( pane ).outerHeight(),
			paneLeft = $( pane ).offset().left,
			paneRight = paneLeft + $( pane ).outerWidth(),
			dragMove = function( event ) {
				var x = event.pageX,
					y = event.pageY;

				deltaX = x < paneLeft ? x - paneLeft :
					x > paneRight ? x - paneRight :
					0;

				deltaY = y < paneTop ? y - paneTop :
					y > paneBottom ? y - paneBottom :
					0;
			},

			dragPoll = function() {
				if ( settings.horizontalScrolling && deltaX ) {
					scrollHorizontal(pane, parseInt( deltaX / 4, 10 ));
				}
				if ( settings.verticalScrolling && deltaY ) {
					scrollVertical( pane, parseInt( deltaY / 4, 10 ));
				}
				if ( dragging ) {
					reqAnimFrame( dragPoll );
				}
			},

			dragEnd = function() {
				dragging = false;
				$( doc )
					.off( 'mousemove.enscroll.pane' )
					.off( 'mouseup.enscroll.pane' );
			};

		reqAnimFrame( dragPoll );

		$( doc ).on({
			'mousemove.enscroll.pane': dragMove,
			'mouseup.enscroll.pane': dragEnd
		});
	},

	touchStart = function( event ) {
		var touchX, touchY, touchAxis, touchX0, touchY0, touchStarted, touchDelta,
			pane = this,
			touchMove = function( event ) {
				touchX = event.touches[0].clientX;
				touchY = event.touches[0].clientY;

				if ( !touchAxis ) {
					touchAxis = touchY === touchY0 && touchX === touchX0 ? undefined :
						Math.abs( touchY0 - touchY ) > Math.abs( touchX0 - touchX ) ? 'y' :
						'x';
				}

				preventDefault( event );
			},

			touchPoll = function() {
				if ( !touchStarted ) {
					return;
				}

				if ( touchAxis === 'y' ) {
					scrollVertical( pane, touchY0 - touchY );
					touchDelta = touchY0 - touchY;
					touchY0 = touchY;
				} else if ( touchAxis === 'x' ) {
					scrollHorizontal( pane, touchX0 - touchX );
					touchDelta = touchX0 - touchX;
					touchX0 = touchX;
				}

				reqAnimFrame( touchPoll );
			},

			touchEnd = function() {
				var t = 0,
					d = Math.abs( touchDelta * 1.5 );

				this.removeEventListener( 'touchmove', touchMove, false );
				this.removeEventListener( 'touchend', touchEnd, false );
				touchStarted = false;

				reqAnimFrame( function touchFinish() {
					var dx;

					if ( t === d || touchStarted ) {
						return;
					}

					dx = easeOutExpo( touchDelta, d, t );

					if ( !isNaN( dx ) && dx !== 0 ) {
						t += 1;
						if ( touchAxis === 'y' ) {
							scrollVertical( pane, dx );
						} else {
							scrollHorizontal( pane, dx );
						}

						reqAnimFrame( touchFinish );
					}
				});
			};

		if ( event.touches.length === 1 ) {
			touchX0 = event.touches[0].clientX;
			touchY0 = event.touches[0].clientY;
			touchStarted = true;
			this.addEventListener( 'touchmove', touchMove, false );
			this.addEventListener( 'touchend', touchEnd, false );
			reqAnimFrame( touchPoll );
		}
	},

	api = {
		reposition: function() {
			return this.each(function() {
				var $this = $( this ),
					data = $this.data( 'enscroll' ),
					positionElem = function( elem, x, y ) {
						elem.style.left = x + 'px';
						elem.style.top = y + 'px';
					},
					corner, trackWrapper, offset;

				if ( data ) {
					offset = $this.position();
					corner = data.corner;
					if ( data.settings.verticalScrolling ) {
						trackWrapper = data.verticalTrackWrapper;
						positionElem( trackWrapper,
                            ( data.settings.verticalScrollerSide === 'right' ? offset.left + $this.outerWidth() - $( trackWrapper ).width() - getComputedValue( this, 'border-right-width' ) : offset.left + getComputedValue( this, 'border-left-width' )),
							offset.top + getComputedValue( this, 'border-top-width' ));
					}

					if ( data.settings.horizontalScrolling ) {
						trackWrapper = data.horizontalTrackWrapper;
						positionElem( trackWrapper,
							offset.left + getComputedValue( this, 'border-left-width' ),
							offset.top + $this.outerHeight() - $( trackWrapper ).height() - getComputedValue( this, 'border-bottom-width' ));
					}

					if ( corner ) {
						positionElem( corner,
							offset.left + $this.outerWidth() - $( corner ).outerWidth() - getComputedValue( this, 'border-right-width' ),
							offset.top + $this.outerHeight() - $( corner ).outerHeight() - getComputedValue( this, 'border-bottom-width' ));
					}
				}
			});
		},

		resize: function() {
			return this.each(function() {
				var $this = $( this ),
					data = $this.data( 'enscroll' ),
					settings, paneHeight, paneWidth,
					trackWrapper, pct, track, trackWidth, trackHeight,
					$scrollUpBtn, $scrollDownBtn, $scrollLeftBtn, $scrollRightBtn,
					handle, handleWidth, handleHeight, prybar;

				if ( !data ) {
					return true;
				}

				settings = data.settings;

				if ( $this.is( ':visible' )) {
					if ( settings.verticalScrolling ) {
						trackWrapper = data.verticalTrackWrapper;
						paneHeight = $this.innerHeight();
						pct = paneHeight / this.scrollHeight;
						track = $( trackWrapper ).find( '.enscroll-track' )[0];
						$scrollUpBtn = $( trackWrapper ).find( '.' + settings.scrollUpButtonClass );
						$scrollDownBtn = $(trackWrapper).find( '.' + settings.scrollDownButtonClass );
						
						//Updated by Mohammad AlBanna
						//MBanna.me
						//Check if the array has elements, empty array returns undefined value when trying to get width/height in jQuery >= 3.0
						//**********//
						$scrollUpBtnHeight = 0;
						$scrollDownBtnHeight = 0;

						if($scrollUpBtn.length != 0){
							$scrollUpBtnHeight = $scrollUpBtn[0].outerHeight();
						}
						if($scrollDownBtn.length != 0){
							$scrollDownBtnHeight = $scrollDownBtn[0].outerHeight();
						}

						trackHeight = paneHeight;
						trackHeight -= $( track ).outerHeight() - $( track ).height() + $scrollUpBtnHeight + $scrollDownBtnHeight;
						//**********//
						handle = track.firstChild;
						handleHeight = Math.max( pct * trackHeight,
							settings.minScrollbarLength );
						handleHeight -= $( handle ).outerHeight() - $( handle ).height();

						// hide the track first -- this causes less reflows and
						// fixes an IE8 bug that prevents background images
						// from being redrawn
						trackWrapper.style.display = 'none';
						track.style.height = trackHeight + 'px';
						handle.style.height = handleHeight + 'px';
						if ( pct < 1 ) {
							pct = $this.scrollTop() / ( this.scrollHeight - $this.height() );
							handle.style.top = ( pct * ( trackHeight - handleHeight ) ) + 'px';
							trackWrapper.style.display = 'block';
						}
					}

					if ( settings.horizontalScrolling ) {
						trackWrapper = data.horizontalTrackWrapper;
						paneWidth = $this.innerWidth();
						pct = paneWidth / this.scrollWidth;
						track = $( trackWrapper ).find( '.enscroll-track' )[0];
						$scrollLeftBtn = $( trackWrapper ).find( '.' + settings.scrollLeftButtonClass );
						$scrollRightBtn = $( trackWrapper ).find( '.' + settings.scrollRightButtonClass );

						//Updated by Mohammad AlBanna
						//MBanna.me
						//Check if the array has elements, empty array returns undefined value when trying to get width/height in jQuery >= 3.0
						//**********//
						$scrollLeftBtnWidth = 0;
						$scrollRightBtnWidth = 0;

						if($scrollUpBtn.length != 0){
							$scrollLeftBtnWidth = $scrollUpBtn[0].outerWidth();
						}
						if($scrollDownBtn.length != 0){
							$scrollRightBtnWidth = $scrollDownBtn[0].outerWidth();
						}

						trackWidth = settings.verticalScrolling ?
							paneWidth - $( data.verticalTrackWrapper ).find( '.enscroll-track' ).outerWidth() :
							paneWidth;
						trackWidth -= $( track ).outerWidth() - $( track ).width() + $scrollLeftBtnWidth + $scrollRightBtnWidth;
						//**********//
						handle = track.firstChild;
						handleWidth = Math.max( pct * trackWidth,
							settings.minScrollbarLength);
						handleWidth -= $( handle ).outerWidth() - $( handle ).width();

						// see comment above
						trackWrapper.style.display = 'none';
						track.style.width = trackWidth + 'px';
						handle.style.width = handleWidth + 'px';
						if ( pct < 1 ) {
							pct = $this.scrollLeft() / ( this.scrollWidth - $this.width() );
							handle.style.left = ( pct * ( trackWidth - handleWidth ) ) + 'px';
							trackWrapper.style.display = 'block';
						}

						if ( data._prybar ) {
							prybar = data._prybar;
							if (prybar.parentNode != null) {
								prybar.parentNode.removeChild(prybar);
							}
							
							if ( settings.verticalScrolling ) {
								prybar.style.width = ( this.scrollWidth) + 'px';
								this.appendChild( prybar );
							}
						}
					}
					if ( data.corner ) {
						data.corner.style.display = data.verticalTrackWrapper && data.horizontalTrackWrapper && $( data.verticalTrackWrapper ).is( ':visible' ) && $( data.horizontalTrackWrapper ).is( ':visible' ) ? '' : 'none';
					}
				} else {
					if ( settings.verticalScrolling ) {
						data.verticalTrackWrapper.style.display = 'none';
					}
					if ( settings.horizontalScrolling ) {
						data.horizontalTrackWrapper.style.display = 'none';
					}
					if ( data.corner ) {
						data.corner.style.display = 'none';
					}
				}

			});
		},

		startPolling: function() {
			return this.each(function() {
				var data = $( this ).data( 'enscroll' ),
					pane = this,
					$pane = $( pane ),
					paneWidth = -1,
					paneHeight = -1,
					paneScrollWidth = -1,
					paneScrollHeight = -1,
					paneOffset,

					paneChangeListener = function() {
						if ( data.settings.pollChanges ) {
							var sw = pane.scrollWidth,
								sh = pane.scrollHeight,
								pw = $pane.width(),
								ph = $pane.height(),
								offset = $pane.offset();

							if ( data.settings.verticalScrolling &&
									( ph !== paneHeight || sh !== paneScrollHeight ) ||
								data.settings.horizontalScrolling &&
									( pw !== paneWidth || sw !== paneScrollWidth ) ) {
								paneScrollWidth = sw;
								paneScrollHeight = sh;

								api.resize.call( $pane );
							}

							if ( paneOffset.left !== offset.left ||
									paneOffset.top !== offset.top ||
									pw !== paneWidth ||
									ph !== paneHeight ) {

								paneOffset = offset;
								paneWidth = pw;
								paneHeight = ph;

								api.reposition.call( $pane );
							}

							setTimeout( paneChangeListener, 350 );
						}
					};

				if ( data ) {
					data.settings.pollChanges = true;
					paneScrollHeight = pane.scrollHeight;
					paneScrollWidth = pane.scrollWidth;
					paneOffset = $pane.offset();
					paneChangeListener();
				}
			});
		},

		stopPolling: function() {
			return this.each(function() {
				var data = $( this ).data( 'enscroll' );
				if ( data ) {
					data.settings.pollChanges = false;
				}
			});
		},

		destroy: function() {
			return this.each(function() {
				var $this = $( this ),
					data = $this.data( 'enscroll' ),
					trackWrapper, mouseScrollHandler;
				if ( data ) {

					api.stopPolling.call( $this );

					mouseScrollHandler = data._mouseScrollHandler;

					if ( data.settings.verticalScrolling ) {
						trackWrapper = data.verticalTrackWrapper;

						$( trackWrapper ).remove();
						trackWrapper = null;
					}

					if ( data.settings.horizontalScrolling ) {
						trackWrapper = data.horizontalTrackWrapper;

						$( trackWrapper ).remove();
						trackWrapper = null;
					}

					// clear the fade timer to prevent an error being thrown
					// when the plugin object is destroyed while the fading
					// scrollbar is visible - shoutout to gpurves
					if ( data._fadeTimer ) {
						clearTimeout( data._fadeTimer );
					}

					if ( data.corner ) {
						$( data.corner ).remove();
					}

					if ( data._prybar && data._prybar.parentNode && data._prybar.parentNode === this ) {
						$( data._prybar ).remove();
					}

					this.setAttribute( 'style', data._style || '' );

					if ( !data._hadTabIndex ) {
						$this.removeAttr( 'tabindex' );
					}

					$this
						.off( 'scroll.enscroll.pane' )
						.off( 'keydown.enscroll.pane' )
						.off( 'mouseenter.enscroll.pane' )
						.off( 'mousedown.enscroll.pane' )
						.data( 'enscroll', null );

					if ( this.removeEventListener ) {
						this.removeEventListener( 'wheel', mouseScrollHandler, false );
						this.removeEventListener( 'mousewheel', mouseScrollHandler, false );
						this.removeEventListener( 'touchstart', touchStart, false );
					} else if ( this.detachEvent ) {
						this.detachEvent( 'onmousewheel', mouseScrollHandler );
					}

					$( win ).off( 'resize.enscroll.window' );
				}
			});
		}
	};


	$.fn.enscroll = function( opts ) {

		var settings;
		// handle API method calls
		if ( api[opts] ) {
			return api[opts].call( this );
		}
		// otherwise, initialize the enscroll element

		// use default settings, and overwrite defaults with options passed in
		settings = $.extend( {}, defaultSettings, opts );

		return this.each( function() {

			// don't apply this plugin when both scrolling settings are false
			if ( !settings.verticalScrolling && !settings.horizontalScrolling ) {
				return;
			}

			var $this = $( this ),
				pane = this,
				oldStyle = $this.attr( 'style' ),
				hadTabIndex = true,
				horizontalTrackWrapper, verticalTrackWrapper,
				horizontalTrack, verticalTrack,
				horizontalHandle, verticalHandle,
				verticalUpButton, verticalDownButton,
				horizontalLeftButton, horizontalRightButton,
				trackHeight, trackWidth,
				corner, outline, tabindex,
				outlineWidth, prybar, paddingSide,
				trackWrapperCSS = {
					'position': 'absolute',
					'z-index': settings.zIndex,
					'margin': 0,
					'padding': 0
				},

				// closures to bind events to handlers
				mouseScrollHandler = function( event ) {
					mouseScroll.call( pane, event );
				},
				addHandleHTML = function( handle, html ) {
					if ( typeof html === 'string' ) {
						$( handle ).html( html );
					} else {
						handle.appendChild( html );
					}
				};

			// if we want vertical scrolling, create and initialize
			// the horizontal scrollbar and its components
			if ( settings.verticalScrolling ) {
				verticalTrackWrapper = doc.createElement( 'div' );
				verticalTrack = doc.createElement( 'div' );
				verticalHandle = doc.createElement( 'a' );

				$( verticalTrack )
					.css( 'position', 'relative' )
					.addClass( 'enscroll-track' )
					.addClass( settings.verticalTrackClass )
					.appendTo( verticalTrackWrapper );

				if ( settings.drawScrollButtons ) {
					verticalUpButton = doc.createElement( 'a' );
					verticalDownButton = doc.createElement( 'a' );

					$( verticalUpButton )
						.css({
							'display': 'block',
							'text-decoration': 'none'
						})
						.attr( 'href', '' )
						.html( '&nbsp;' )
						.addClass( settings.scrollUpButtonClass )
						.on( 'click', function() {
							scrollVertical( pane, -settings.scrollIncrement );
							return false;
						})
						.insertBefore( verticalTrack );

					$( verticalDownButton )
						.css({
							'display': 'block',
							'text-decoration': 'none'
						})
						.attr( 'href', '' )
						.html( '&nbsp;' )
						.on( 'click', function() {
							scrollVertical( pane, settings.scrollIncrement );
							return false;
						})
						.addClass( settings.scrollDownButtonClass )
						.appendTo( verticalTrackWrapper );
				}

				if ( settings.clickTrackToScroll ) {
					$( verticalTrack ).on( 'click', function( event ) {
						if ( event.target === this ) {
							scrollAnimateVertical( $this,
								event.pageY > $( verticalHandle ).offset().top ? $this.height() :
								-$this.height() );
						}
					});
				}

				$( verticalHandle )
					.css({
						'position': 'absolute',
						'z-index': 1
					})
					.attr( 'href', '' )
					.addClass( settings.verticalHandleClass )
					.mousedown( { pane: this }, startVerticalDrag )
					.click( function() { return false; })
					.appendTo( verticalTrack );

				addHandleHTML( verticalHandle, settings.verticalHandleHTML );

				$( verticalTrackWrapper )
					.css( trackWrapperCSS )
					.insertAfter( this );

				if ( settings.showOnHover ) {
					$( verticalTrackWrapper )
						.css( 'opacity', 0 )
						.on( 'mouseover.enscroll.vertical', function() {
							showScrollbars.call( pane, false );
						})
						.on( 'mouseout.enscroll.vertical', function() {
							showScrollbars.call( pane );
						});
				}

				trackWidth = $( verticalTrack ).outerWidth();

				// move the content in the pane over to make room for
				// the vertical scrollbar
				if ( settings.addPaddingToPane ) {
					if ( settings.verticalScrollerSide === 'right' ) {
						paddingSide = {
							'padding-right': ( getComputedValue( this, 'padding-right' ) + trackWidth ) + 'px'
						};
					} else {
						paddingSide = {
							'padding-left': ( getComputedValue( this, 'padding-left' ) + trackWidth ) + 'px'
						};
					}

					if( ($this.width() - trackWidth) > 0 ){
						$this.css($.extend({'width': ( $this.width() - trackWidth ) + 'px'}, paddingSide));
					} else{
						$this.css(paddingSide);
					}
				}

				try {

					outlineWidth = parseInt( $this.css( 'outline-width' ), 10 );

					if (( outlineWidth === 0 || isNaN( outlineWidth )) &&
						$this.css( 'outline-style') === 'none' ) {
						$this.css( 'outline', 'none' );
					}
				} catch( ex ) {
					$this.css( 'outline', 'none' );
				}
			}

			// if we want horizontal scrolling, create the elements for and
			// initialize the horizontal track and handle
			if ( settings.horizontalScrolling ) {

				horizontalTrackWrapper = doc.createElement( 'div' );
				horizontalTrack = doc.createElement( 'div' );
				horizontalHandle = doc.createElement( 'a' );

				$( horizontalTrack )
					.css({
						'position': 'relative',
						'z-index': 1
					})
					.addClass( 'enscroll-track' )
					.addClass( settings.horizontalTrackClass )
					.appendTo( horizontalTrackWrapper );

				if ( settings.drawScrollButtons ) {
					horizontalLeftButton = doc.createElement( 'a' );
					horizontalRightButton = doc.createElement( 'a' );

					$( horizontalLeftButton )
						.css( 'display', 'block' )
						.attr( 'href', '' )
						.on( 'click', function() {
							scrollHorizontal( pane, -settings.scrollIncrement );
							return false;
						})
						.addClass( settings.scrollLeftButtonClass )
						.insertBefore( horizontalTrack );

					$( horizontalRightButton )
						.css( 'display', 'block' )
						.attr( 'href', '' )
						.on( 'click', function() {
							scrollHorizontal( pane, settings.scrollIncrement );
							return false;
						})
						.addClass( settings.scrollRightButtonClass )
						.appendTo( horizontalTrackWrapper );
				}

				if ( settings.clickTrackToScroll ) {
					$( horizontalTrack).on( 'click', function( event ) {
						if ( event.target === this ) {
							scrollAnimateHorizontal( $this,
								event.pageX > $(horizontalHandle).offset().left ? $this.width() :
								-$this.width() );
						}
					});
				}

				$( horizontalHandle )
					.css({
						'position': 'absolute',
						'z-index': 1
					})
					.attr( 'href', '' )
					.addClass( settings.horizontalHandleClass )
					.click( function() { return false; })
					.mousedown( { pane: this }, startHorizontalDrag )
					.appendTo( horizontalTrack );

				addHandleHTML( horizontalHandle, settings.horizontalHandleHTML );

				$( horizontalTrackWrapper )
					.css( trackWrapperCSS )
					.insertAfter( this );

				if ( settings.showOnHover ) {
					$( horizontalTrackWrapper )
						.css( 'opacity', 0 )
						.on( 'mouseover.enscroll.horizontal', function() {
							showScrollbars.call( pane, false );
						})
						.on( 'mouseout.enscroll.horizontal', function() {
							showScrollbars.call( pane );
						});
				}

				trackHeight = $( horizontalTrack ).outerHeight();

				if ( settings.addPaddingToPane ) {
					$this.css({
						'height': ( $this.height() - trackHeight ) + 'px',
						'padding-bottom': ( parseInt( $this.css( 'padding-bottom' ), 10 ) + trackHeight ) + 'px'
					});
				}

				// we need to add an element to the pane in order to
				// stretch to the scrollWidth of the pane so the content
				// scrolls horizontally beyond the vertical scrollbar
				if ( settings.verticalScrolling ) {
					prybar = document.createElement( 'div' );
					$( prybar )
						.css({
							'width': '1px',
							'height': '1px',
							'visibility': 'hidden',
							'padding': 0,
							'margin': '-1px'
						})
						.appendTo( this );
				}
			}

			if ( settings.verticalScrolling && settings.horizontalScrolling && settings.drawCorner ) {
				corner = doc.createElement( 'div' );
				$( corner )
					.addClass( settings.cornerClass )
					.css( trackWrapperCSS )
					.insertAfter( this );
			}

			// add a tabindex attribute to the pane if it doesn't already have one
			// if the element does not have a tabindex in IE6, undefined is returned,
			// all other browsers return an empty string
			tabindex = $this.attr( 'tabindex' );
			if ( !tabindex ) {
				$this.attr( 'tabindex', 0 );
				hadTabIndex = false;
			}

			// if the outline style is not specified in IE6/7/8, null is returned
			// all other browsers return an empty string
			try {
				outline = $this.css( 'outline' );
				if ( !outline || outline.length < 1 ) {
					$this.css( 'outline', 'none' );
				}
			} catch(ex) {
				$this.css( 'outline', 'none' );
			}

			// register an handler that listens for the pane to scroll, and
			// sync the scrollbars' positions
			$this
				.on({
					'scroll.enscroll.pane': function( event ) {
						paneScrolled.call( this, event );
					},
					'keydown.enscroll.pane': keyHandler,
					'mousedown.enscroll.pane': dragHandler
				})
				.css( 'overflow', 'hidden' )
				// store the data we need for handling events and destruction
				.data( 'enscroll', {
					settings: settings,
					horizontalTrackWrapper: horizontalTrackWrapper,
					verticalTrackWrapper: verticalTrackWrapper,
					corner: corner,
					_prybar: prybar,
					_mouseScrollHandler: mouseScrollHandler,
					_hadTabIndex: hadTabIndex,
					_style: oldStyle,
					_scrollingX: false,
					_scrollingY: false,
					_startX: 0,
					_startY: 0,
					_endX: 0,
					_endY: 0,
					_duration: parseInt( settings.easingDuration / 16.66666, 10 ),
					_scrollHeightNoPadding: testScrollHeight( this.nodeName )
				});

			// reposition the scrollbars if the window is resized
			$( win ).on( 'resize.enscroll.window', function() {
				api.reposition.call( $this );
			});

			// if showOnHover is set, attach the hover listeners
			if ( settings.showOnHover ) {
				$this.on( 'mouseenter.enscroll.pane', function() {
					showScrollbars.call( this );
				});
			}

			// listen for mouse wheel and touch events and scroll appropriately
			if ( this.addEventListener ) {
				if ( 'onwheel' in this || 'WheelEvent' in win &&
					navigator.userAgent.toLowerCase().indexOf( 'msie' ) >= 0 ) {
					this.addEventListener( 'wheel', mouseScrollHandler, false );
				} else if ( 'onmousewheel' in this ) {
					this.addEventListener( 'mousewheel', mouseScrollHandler, false );
				}

				this.addEventListener( 'touchstart', touchStart, false );
			} else if ( this.attachEvent ) {
				// oldie love
				this.attachEvent( 'onmousewheel', mouseScrollHandler );
			}

			// start polling for changes in dimension and position
			if ( settings.pollChanges ) {
				api.startPolling.call( $this );
			}

			api.resize.call( $this );
			api.reposition.call( $this );

		});

	};

}( jQuery, window, document ));
/*! jQuery UI - v1.11.4 - 2015-08-26
* http://jqueryui.com
* Includes: core.js, widget.js, mouse.js, position.js, draggable.js, resizable.js
* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */

//If the file has injected many times
if($(".jsContainer").length >= 1){
    throw new Error("Injected!");
}

(function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define([ "jquery" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
}(function( $ ) {

/*!
 * jQuery UI Core 1.11.4
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/category/ui-core/
 */


// $.ui might exist from components with no dependencies, e.g., $.ui.position
$.ui = $.ui || {};

$.extend( $.ui, {
	version: "1.11.4",

	keyCode: {
		BACKSPACE: 8,
		COMMA: 188,
		DELETE: 46,
		DOWN: 40,
		END: 35,
		ENTER: 13,
		ESCAPE: 27,
		HOME: 36,
		LEFT: 37,
		PAGE_DOWN: 34,
		PAGE_UP: 33,
		PERIOD: 190,
		RIGHT: 39,
		SPACE: 32,
		TAB: 9,
		UP: 38
	}
});

// plugins
$.fn.extend({
	scrollParent: function( includeHidden ) {
		var position = this.css( "position" ),
			excludeStaticParent = position === "absolute",
			overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
			scrollParent = this.parents().filter( function() {
				var parent = $( this );
				if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
					return false;
				}
				return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) );
			}).eq( 0 );

		return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent;
	},

	uniqueId: (function() {
		var uuid = 0;

		return function() {
			return this.each(function() {
				if ( !this.id ) {
					this.id = "ui-id-" + ( ++uuid );
				}
			});
		};
	})(),

	removeUniqueId: function() {
		return this.each(function() {
			if ( /^ui-id-\d+$/.test( this.id ) ) {
				$( this ).removeAttr( "id" );
			}
		});
	}
});

// selectors
function focusable( element, isTabIndexNotNaN ) {
	var map, mapName, img,
		nodeName = element.nodeName.toLowerCase();
	if ( "area" === nodeName ) {
		map = element.parentNode;
		mapName = map.name;
		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
			return false;
		}
		img = $( "img[usemap='#" + mapName + "']" )[ 0 ];
		return !!img && visible( img );
	}
	return ( /^(input|select|textarea|button|object)$/.test( nodeName ) ?
		!element.disabled :
		"a" === nodeName ?
			element.href || isTabIndexNotNaN :
			isTabIndexNotNaN) &&
		// the element and all of its ancestors must be visible
		visible( element );
}

function visible( element ) {
	return $.expr.filters.visible( element ) &&
		!$( element ).parents().addBack().filter(function() {
			return $.css( this, "visibility" ) === "hidden";
		}).length;
}

$.extend( $.expr[ ":" ], {
	data: $.expr.createPseudo ?
		$.expr.createPseudo(function( dataName ) {
			return function( elem ) {
				return !!$.data( elem, dataName );
			};
		}) :
		// support: jQuery <1.8
		function( elem, i, match ) {
			return !!$.data( elem, match[ 3 ] );
		},

	focusable: function( element ) {
		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
	},

	tabbable: function( element ) {
		var tabIndex = $.attr( element, "tabindex" ),
			isTabIndexNaN = isNaN( tabIndex );
		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
	}
});

// support: jQuery <1.8
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
	$.each( [ "Width", "Height" ], function( i, name ) {
		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
			type = name.toLowerCase(),
			orig = {
				innerWidth: $.fn.innerWidth,
				innerHeight: $.fn.innerHeight,
				outerWidth: $.fn.outerWidth,
				outerHeight: $.fn.outerHeight
			};

		function reduce( elem, size, border, margin ) {
			$.each( side, function() {
				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
				if ( border ) {
					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
				}
				if ( margin ) {
					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
				}
			});
			return size;
		}

		$.fn[ "inner" + name ] = function( size ) {
			if ( size === undefined ) {
				return orig[ "inner" + name ].call( this );
			}

			return this.each(function() {
				$( this ).css( type, reduce( this, size ) + "px" );
			});
		};

		$.fn[ "outer" + name] = function( size, margin ) {
			if ( typeof size !== "number" ) {
				return orig[ "outer" + name ].call( this, size );
			}

			return this.each(function() {
				$( this).css( type, reduce( this, size, true, margin ) + "px" );
			});
		};
	});
}

// support: jQuery <1.8
if ( !$.fn.addBack ) {
	$.fn.addBack = function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	};
}

// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
	$.fn.removeData = (function( removeData ) {
		return function( key ) {
			if ( arguments.length ) {
				return removeData.call( this, $.camelCase( key ) );
			} else {
				return removeData.call( this );
			}
		};
	})( $.fn.removeData );
}

// deprecated
$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );

$.fn.extend({
	focus: (function( orig ) {
		return function( delay, fn ) {
			return typeof delay === "number" ?
				this.each(function() {
					var elem = this;
					setTimeout(function() {
						$( elem ).focus();
						if ( fn ) {
							fn.call( elem );
						}
					}, delay );
				}) :
				orig.apply( this, arguments );
		};
	})( $.fn.focus ),

	disableSelection: (function() {
		var eventType = "onselectstart" in document.createElement( "div" ) ?
			"selectstart" :
			"mousedown";

		return function() {
			return this.bind( eventType + ".ui-disableSelection", function( event ) {
				event.preventDefault();
			});
		};
	})(),

	enableSelection: function() {
		return this.unbind( ".ui-disableSelection" );
	},

	zIndex: function( zIndex ) {
		if ( zIndex !== undefined ) {
			return this.css( "zIndex", zIndex );
		}

		if ( this.length ) {
			var elem = $( this[ 0 ] ), position, value;
			while ( elem.length && elem[ 0 ] !== document ) {
				// Ignore z-index if position is set to a value where z-index is ignored by the browser
				// This makes behavior of this function consistent across browsers
				// WebKit always returns auto if the element is positioned
				position = elem.css( "position" );
				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
					// IE returns 0 when zIndex is not specified
					// other browsers return a string
					// we ignore the case of nested elements with an explicit value of 0
					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
					value = parseInt( elem.css( "zIndex" ), 10 );
					if ( !isNaN( value ) && value !== 0 ) {
						return value;
					}
				}
				elem = elem.parent();
			}
		}

		return 0;
	}
});

// $.ui.plugin is deprecated. Use $.widget() extensions instead.
$.ui.plugin = {
	add: function( module, option, set ) {
		var i,
			proto = $.ui[ module ].prototype;
		for ( i in set ) {
			proto.plugins[ i ] = proto.plugins[ i ] || [];
			proto.plugins[ i ].push( [ option, set[ i ] ] );
		}
	},
	call: function( instance, name, args, allowDisconnected ) {
		var i,
			set = instance.plugins[ name ];

		if ( !set ) {
			return;
		}

		if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
			return;
		}

		for ( i = 0; i < set.length; i++ ) {
			if ( instance.options[ set[ i ][ 0 ] ] ) {
				set[ i ][ 1 ].apply( instance.element, args );
			}
		}
	}
};


/*!
 * jQuery UI Widget 1.11.4
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/jQuery.widget/
 */


var widget_uuid = 0,
	widget_slice = Array.prototype.slice;

$.cleanData = (function( orig ) {
	return function( elems ) {
		var events, elem, i;
		for ( i = 0; (elem = elems[i]) != null; i++ ) {
			try {

				// Only trigger remove when necessary to save time
				events = $._data( elem, "events" );
				if ( events && events.remove ) {
					$( elem ).triggerHandler( "remove" );
				}

			// http://bugs.jquery.com/ticket/8235
			} catch ( e ) {}
		}
		orig( elems );
	};
})( $.cleanData );

$.widget = function( name, base, prototype ) {
	var fullName, existingConstructor, constructor, basePrototype,
		// proxiedPrototype allows the provided prototype to remain unmodified
		// so that it can be used as a mixin for multiple widgets (#8876)
		proxiedPrototype = {},
		namespace = name.split( "." )[ 0 ];

	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
		return !!$.data( elem, fullName );
	};

	$[ namespace ] = $[ namespace ] || {};
	existingConstructor = $[ namespace ][ name ];
	constructor = $[ namespace ][ name ] = function( options, element ) {
		// allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new constructor( options, element );
		}

		// allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};
	// extend with the existing constructor to carry over any static properties
	$.extend( constructor, existingConstructor, {
		version: prototype.version,
		// copy the object used to create the prototype in case we need to
		// redefine the widget later
		_proto: $.extend( {}, prototype ),
		// track widgets that inherit from this widget in case this widget is
		// redefined after a widget inherits from it
		_childConstructors: []
	});

	basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.widget.extend( {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( !$.isFunction( value ) ) {
			proxiedPrototype[ prop ] = value;
			return;
		}
		proxiedPrototype[ prop ] = (function() {
			var _super = function() {
					return base.prototype[ prop ].apply( this, arguments );
				},
				_superApply = function( args ) {
					return base.prototype[ prop ].apply( this, args );
				};
			return function() {
				var __super = this._super,
					__superApply = this._superApply,
					returnValue;

				this._super = _super;
				this._superApply = _superApply;

				returnValue = value.apply( this, arguments );

				this._super = __super;
				this._superApply = __superApply;

				return returnValue;
			};
		})();
	});
	constructor.prototype = $.widget.extend( basePrototype, {
		// TODO: remove support for widgetEventPrefix
		// always use the name + a colon as the prefix, e.g., draggable:start
		// don't prefix for widgets that aren't DOM-based
		widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
	}, proxiedPrototype, {
		constructor: constructor,
		namespace: namespace,
		widgetName: name,
		widgetFullName: fullName
	});

	// If this widget is being redefined then we need to find all widgets that
	// are inheriting from it and redefine all of them so that they inherit from
	// the new version of this widget. We're essentially trying to replace one
	// level in the prototype chain.
	if ( existingConstructor ) {
		$.each( existingConstructor._childConstructors, function( i, child ) {
			var childPrototype = child.prototype;

			// redefine the child widget using the same prototype that was
			// originally used, but inherit from the new version of the base
			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
		});
		// remove the list of existing child constructors from the old constructor
		// so the old child constructors can be garbage collected
		delete existingConstructor._childConstructors;
	} else {
		base._childConstructors.push( constructor );
	}

	$.widget.bridge( name, constructor );

	return constructor;
};

$.widget.extend = function( target ) {
	var input = widget_slice.call( arguments, 1 ),
		inputIndex = 0,
		inputLength = input.length,
		key,
		value;
	for ( ; inputIndex < inputLength; inputIndex++ ) {
		for ( key in input[ inputIndex ] ) {
			value = input[ inputIndex ][ key ];
			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
				// Clone objects
				if ( $.isPlainObject( value ) ) {
					target[ key ] = $.isPlainObject( target[ key ] ) ?
						$.widget.extend( {}, target[ key ], value ) :
						// Don't extend strings, arrays, etc. with objects
						$.widget.extend( {}, value );
				// Copy everything else by reference
				} else {
					target[ key ] = value;
				}
			}
		}
	}
	return target;
};

$.widget.bridge = function( name, object ) {
	var fullName = object.prototype.widgetFullName || name;
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = widget_slice.call( arguments, 1 ),
			returnValue = this;

		if ( isMethodCall ) {
			this.each(function() {
				var methodValue,
					instance = $.data( this, fullName );
				if ( options === "instance" ) {
					returnValue = instance;
					return false;
				}
				if ( !instance ) {
					return $.error( "cannot call methods on " + name + " prior to initialization; " +
						"attempted to call method '" + options + "'" );
				}
				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
				}
				methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue && methodValue.jquery ?
						returnValue.pushStack( methodValue.get() ) :
						methodValue;
					return false;
				}
			});
		} else {

			// Allow multiple hashes to be passed on init
			if ( args.length ) {
				options = $.widget.extend.apply( null, [ options ].concat(args) );
			}

			this.each(function() {
				var instance = $.data( this, fullName );
				if ( instance ) {
					instance.option( options || {} );
					if ( instance._init ) {
						instance._init();
					}
				} else {
					$.data( this, fullName, new object( options, this ) );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",
	options: {
		disabled: false,

		// callbacks
		create: null
	},
	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.uuid = widget_uuid++;
		this.eventNamespace = "." + this.widgetName + this.uuid;

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();

		if ( element !== this ) {
			$.data( element, this.widgetFullName, this );
			this._on( true, this.element, {
				remove: function( event ) {
					if ( event.target === element ) {
						this.destroy();
					}
				}
			});
			this.document = $( element.style ?
				// element within the document
				element.ownerDocument :
				// element is window or document
				element.document || element );
			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
		}

		this.options = $.widget.extend( {},
			this.options,
			this._getCreateOptions(),
			options );

		this._create();
		this._trigger( "create", null, this._getCreateEventData() );
		this._init();
	},
	_getCreateOptions: $.noop,
	_getCreateEventData: $.noop,
	_create: $.noop,
	_init: $.noop,

	destroy: function() {
		this._destroy();
		// we can probably remove the unbind calls in 2.0
		// all event bindings should go through this._on()
		this.element
			.unbind( this.eventNamespace )
			.removeData( this.widgetFullName )
			// support: jquery <1.6.3
			// http://bugs.jquery.com/ticket/9413
			.removeData( $.camelCase( this.widgetFullName ) );
		this.widget()
			.unbind( this.eventNamespace )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetFullName + "-disabled " +
				"ui-state-disabled" );

		// clean up events and states
		this.bindings.unbind( this.eventNamespace );
		this.hoverable.removeClass( "ui-state-hover" );
		this.focusable.removeClass( "ui-state-focus" );
	},
	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key,
			parts,
			curOption,
			i;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.widget.extend( {}, this.options );
		}

		if ( typeof key === "string" ) {
			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
			options = {};
			parts = key.split( "." );
			key = parts.shift();
			if ( parts.length ) {
				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
				for ( i = 0; i < parts.length - 1; i++ ) {
					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
					curOption = curOption[ parts[ i ] ];
				}
				key = parts.pop();
				if ( arguments.length === 1 ) {
					return curOption[ key ] === undefined ? null : curOption[ key ];
				}
				curOption[ key ] = value;
			} else {
				if ( arguments.length === 1 ) {
					return this.options[ key ] === undefined ? null : this.options[ key ];
				}
				options[ key ] = value;
			}
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var key;

		for ( key in options ) {
			this._setOption( key, options[ key ] );
		}

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				.toggleClass( this.widgetFullName + "-disabled", !!value );

			// If the widget is becoming disabled, then nothing is interactive
			if ( value ) {
				this.hoverable.removeClass( "ui-state-hover" );
				this.focusable.removeClass( "ui-state-focus" );
			}
		}

		return this;
	},

	enable: function() {
		return this._setOptions({ disabled: false });
	},
	disable: function() {
		return this._setOptions({ disabled: true });
	},

	_on: function( suppressDisabledCheck, element, handlers ) {
		var delegateElement,
			instance = this;

		// no suppressDisabledCheck flag, shuffle arguments
		if ( typeof suppressDisabledCheck !== "boolean" ) {
			handlers = element;
			element = suppressDisabledCheck;
			suppressDisabledCheck = false;
		}

		// no element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
			delegateElement = this.widget();
		} else {
			element = delegateElement = $( element );
			this.bindings = this.bindings.add( element );
		}

		$.each( handlers, function( event, handler ) {
			function handlerProxy() {
				// allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( !suppressDisabledCheck &&
						( instance.options.disabled === true ||
							$( this ).hasClass( "ui-state-disabled" ) ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			}

			// copy the guid so direct unbinding works
			if ( typeof handler !== "string" ) {
				handlerProxy.guid = handler.guid =
					handler.guid || handlerProxy.guid || $.guid++;
			}

			var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
				eventName = match[1] + instance.eventNamespace,
				selector = match[2];
			if ( selector ) {
				delegateElement.delegate( selector, eventName, handlerProxy );
			} else {
				element.bind( eventName, handlerProxy );
			}
		});
	},

	_off: function( element, eventName ) {
		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
			this.eventNamespace;
		element.unbind( eventName ).undelegate( eventName );

		// Clear the stack to avoid memory leaks (#10056)
		this.bindings = $( this.bindings.not( element ).get() );
		this.focusable = $( this.focusable.not( element ).get() );
		this.hoverable = $( this.hoverable.not( element ).get() );
	},

	_delay: function( handler, delay ) {
		function handlerProxy() {
			return ( typeof handler === "string" ? instance[ handler ] : handler )
				.apply( instance, arguments );
		}
		var instance = this;
		return setTimeout( handlerProxy, delay || 0 );
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._on( element, {
			mouseenter: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-hover" );
			},
			mouseleave: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-hover" );
			}
		});
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._on( element, {
			focusin: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-focus" );
			},
			focusout: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-focus" );
			}
		});
	},

	_trigger: function( type, event, data ) {
		var prop, orig,
			callback = this.options[ type ];

		data = data || {};
		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		// the original event may come from any element
		// so we need to reset the target on the new event
		event.target = this.element[ 0 ];

		// copy original event properties over to the new event
		orig = event.originalEvent;
		if ( orig ) {
			for ( prop in orig ) {
				if ( !( prop in event ) ) {
					event[ prop ] = orig[ prop ];
				}
			}
		}

		this.element.trigger( event, data );
		return !( $.isFunction( callback ) &&
			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
			event.isDefaultPrevented() );
	}
};

$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
		if ( typeof options === "string" ) {
			options = { effect: options };
		}
		var hasOptions,
			effectName = !options ?
				method :
				options === true || typeof options === "number" ?
					defaultEffect :
					options.effect || defaultEffect;
		options = options || {};
		if ( typeof options === "number" ) {
			options = { duration: options };
		}
		hasOptions = !$.isEmptyObject( options );
		options.complete = callback;
		if ( options.delay ) {
			element.delay( options.delay );
		}
		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
			element[ method ]( options );
		} else if ( effectName !== method && element[ effectName ] ) {
			element[ effectName ]( options.duration, options.easing, callback );
		} else {
			element.queue(function( next ) {
				$( this )[ method ]();
				if ( callback ) {
					callback.call( element[ 0 ] );
				}
				next();
			});
		}
	};
});

var widget = $.widget;


/*!
 * jQuery UI Mouse 1.11.4
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/mouse/
 */


var mouseHandled = false;
$( document ).mouseup( function() {
	mouseHandled = false;
});

var mouse = $.widget("ui.mouse", {
	version: "1.11.4",
	options: {
		cancel: "input,textarea,button,select,option",
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var that = this;

		this.element
			.bind("mousedown." + this.widgetName, function(event) {
				return that._mouseDown(event);
			})
			.bind("click." + this.widgetName, function(event) {
				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
					$.removeData(event.target, that.widgetName + ".preventClickEvent");
					event.stopImmediatePropagation();
					return false;
				}
			});

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.unbind("." + this.widgetName);
		if ( this._mouseMoveDelegate ) {
			this.document
				.unbind("mousemove." + this.widgetName, this._mouseMoveDelegate)
				.unbind("mouseup." + this.widgetName, this._mouseUpDelegate);
		}
	},

	_mouseDown: function(event) {
		// don't let more than one widget handle mouseStart
		if ( mouseHandled ) {
			return;
		}

		this._mouseMoved = false;

		// we may have missed mouseup (out of window)
		(this._mouseStarted && this._mouseUp(event));

		this._mouseDownEvent = event;

		var that = this,
			btnIsLeft = (event.which === 1),
			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if (!this.mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				that.mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted = (this._mouseStart(event) !== false);
			if (!this._mouseStarted) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
			$.removeData(event.target, this.widgetName + ".preventClickEvent");
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(event) {
			return that._mouseMove(event);
		};
		this._mouseUpDelegate = function(event) {
			return that._mouseUp(event);
		};

		this.document
			.bind( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.bind( "mouseup." + this.widgetName, this._mouseUpDelegate );

		event.preventDefault();

		mouseHandled = true;
		return true;
	},

	_mouseMove: function(event) {
		// Only check for mouseups outside the document if you've moved inside the document
		// at least once. This prevents the firing of mouseup in the case of IE<9, which will
		// fire a mousemove event if content is placed under the cursor. See #7778
		// Support: IE <9
		if ( this._mouseMoved ) {
			// IE mouseup check - mouseup happened when mouse was out of window
			if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
				return this._mouseUp(event);

			// Iframe mouseup check - mouseup occurred in another document
			} else if ( !event.which ) {
				return this._mouseUp( event );
			}
		}

		if ( event.which || event.button ) {
			this._mouseMoved = true;
		}

		if (this._mouseStarted) {
			this._mouseDrag(event);
			return event.preventDefault();
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted =
				(this._mouseStart(this._mouseDownEvent, event) !== false);
			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
		}

		return !this._mouseStarted;
	},

	_mouseUp: function(event) {
		this.document
			.unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.unbind( "mouseup." + this.widgetName, this._mouseUpDelegate );

		if (this._mouseStarted) {
			this._mouseStarted = false;

			if (event.target === this._mouseDownEvent.target) {
				$.data(event.target, this.widgetName + ".preventClickEvent", true);
			}

			this._mouseStop(event);
		}

		mouseHandled = false;
		return false;
	},

	_mouseDistanceMet: function(event) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - event.pageX),
				Math.abs(this._mouseDownEvent.pageY - event.pageY)
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function(/* event */) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function(/* event */) {},
	_mouseDrag: function(/* event */) {},
	_mouseStop: function(/* event */) {},
	_mouseCapture: function(/* event */) { return true; }
});


/*!
 * jQuery UI Position 1.11.4
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/position/
 */

(function() {

$.ui = $.ui || {};

var cachedScrollbarWidth, supportsOffsetFractions,
	max = Math.max,
	abs = Math.abs,
	round = Math.round,
	rhorizontal = /left|center|right/,
	rvertical = /top|center|bottom/,
	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
	rposition = /^\w+/,
	rpercent = /%$/,
	_position = $.fn.position;

function getOffsets( offsets, width, height ) {
	return [
		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
	];
}

function parseCss( element, property ) {
	return parseInt( $.css( element, property ), 10 ) || 0;
}

function getDimensions( elem ) {
	var raw = elem[0];
	if ( raw.nodeType === 9 ) {
		return {
			width: elem.width(),
			height: elem.height(),
			offset: { top: 0, left: 0 }
		};
	}
	if ( $.isWindow( raw ) ) {
		return {
			width: elem.width(),
			height: elem.height(),
			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
		};
	}
	if ( raw.preventDefault ) {
		return {
			width: 0,
			height: 0,
			offset: { top: raw.pageY, left: raw.pageX }
		};
	}
	return {
		width: elem.outerWidth(),
		height: elem.outerHeight(),
		offset: elem.offset()
	};
}

$.position = {
	scrollbarWidth: function() {
		if ( cachedScrollbarWidth !== undefined ) {
			return cachedScrollbarWidth;
		}
		var w1, w2,
			div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
			innerDiv = div.children()[0];

		$( "body" ).append( div );
		w1 = innerDiv.offsetWidth;
		div.css( "overflow", "scroll" );

		w2 = innerDiv.offsetWidth;

		if ( w1 === w2 ) {
			w2 = div[0].clientWidth;
		}

		div.remove();

		return (cachedScrollbarWidth = w1 - w2);
	},
	getScrollInfo: function( within ) {
		var overflowX = within.isWindow || within.isDocument ? "" :
				within.element.css( "overflow-x" ),
			overflowY = within.isWindow || within.isDocument ? "" :
				within.element.css( "overflow-y" ),
			hasOverflowX = overflowX === "scroll" ||
				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
			hasOverflowY = overflowY === "scroll" ||
				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
		return {
			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
			height: hasOverflowX ? $.position.scrollbarWidth() : 0
		};
	},
	getWithinInfo: function( element ) {
		var withinElement = $( element || window ),
			isWindow = $.isWindow( withinElement[0] ),
			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
		return {
			element: withinElement,
			isWindow: isWindow,
			isDocument: isDocument,
			offset: withinElement.offset() || { left: 0, top: 0 },
			scrollLeft: withinElement.scrollLeft(),
			scrollTop: withinElement.scrollTop(),

			// support: jQuery 1.6.x
			// jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows
			width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(),
			height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight()
		};
	}
};

$.fn.position = function( options ) {
	if ( !options || !options.of ) {
		return _position.apply( this, arguments );
	}

	// make a copy, we don't want to modify arguments
	options = $.extend( {}, options );

	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
		target = $( options.of ),
		within = $.position.getWithinInfo( options.within ),
		scrollInfo = $.position.getScrollInfo( within ),
		collision = ( options.collision || "flip" ).split( " " ),
		offsets = {};

	dimensions = getDimensions( target );
	if ( target[0].preventDefault ) {
		// force left top to allow flipping
		options.at = "left top";
	}
	targetWidth = dimensions.width;
	targetHeight = dimensions.height;
	targetOffset = dimensions.offset;
	// clone to reuse original targetOffset later
	basePosition = $.extend( {}, targetOffset );

	// force my and at to have valid horizontal and vertical positions
	// if a value is missing or invalid, it will be converted to center
	$.each( [ "my", "at" ], function() {
		var pos = ( options[ this ] || "" ).split( " " ),
			horizontalOffset,
			verticalOffset;

		if ( pos.length === 1) {
			pos = rhorizontal.test( pos[ 0 ] ) ?
				pos.concat( [ "center" ] ) :
				rvertical.test( pos[ 0 ] ) ?
					[ "center" ].concat( pos ) :
					[ "center", "center" ];
		}
		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";

		// calculate offsets
		horizontalOffset = roffset.exec( pos[ 0 ] );
		verticalOffset = roffset.exec( pos[ 1 ] );
		offsets[ this ] = [
			horizontalOffset ? horizontalOffset[ 0 ] : 0,
			verticalOffset ? verticalOffset[ 0 ] : 0
		];

		// reduce to just the positions without the offsets
		options[ this ] = [
			rposition.exec( pos[ 0 ] )[ 0 ],
			rposition.exec( pos[ 1 ] )[ 0 ]
		];
	});

	// normalize collision option
	if ( collision.length === 1 ) {
		collision[ 1 ] = collision[ 0 ];
	}

	if ( options.at[ 0 ] === "right" ) {
		basePosition.left += targetWidth;
	} else if ( options.at[ 0 ] === "center" ) {
		basePosition.left += targetWidth / 2;
	}

	if ( options.at[ 1 ] === "bottom" ) {
		basePosition.top += targetHeight;
	} else if ( options.at[ 1 ] === "center" ) {
		basePosition.top += targetHeight / 2;
	}

	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
	basePosition.left += atOffset[ 0 ];
	basePosition.top += atOffset[ 1 ];

	return this.each(function() {
		var collisionPosition, using,
			elem = $( this ),
			elemWidth = elem.outerWidth(),
			elemHeight = elem.outerHeight(),
			marginLeft = parseCss( this, "marginLeft" ),
			marginTop = parseCss( this, "marginTop" ),
			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
			position = $.extend( {}, basePosition ),
			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );

		if ( options.my[ 0 ] === "right" ) {
			position.left -= elemWidth;
		} else if ( options.my[ 0 ] === "center" ) {
			position.left -= elemWidth / 2;
		}

		if ( options.my[ 1 ] === "bottom" ) {
			position.top -= elemHeight;
		} else if ( options.my[ 1 ] === "center" ) {
			position.top -= elemHeight / 2;
		}

		position.left += myOffset[ 0 ];
		position.top += myOffset[ 1 ];

		// if the browser doesn't support fractions, then round for consistent results
		if ( !supportsOffsetFractions ) {
			position.left = round( position.left );
			position.top = round( position.top );
		}

		collisionPosition = {
			marginLeft: marginLeft,
			marginTop: marginTop
		};

		$.each( [ "left", "top" ], function( i, dir ) {
			if ( $.ui.position[ collision[ i ] ] ) {
				$.ui.position[ collision[ i ] ][ dir ]( position, {
					targetWidth: targetWidth,
					targetHeight: targetHeight,
					elemWidth: elemWidth,
					elemHeight: elemHeight,
					collisionPosition: collisionPosition,
					collisionWidth: collisionWidth,
					collisionHeight: collisionHeight,
					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
					my: options.my,
					at: options.at,
					within: within,
					elem: elem
				});
			}
		});

		if ( options.using ) {
			// adds feedback as second argument to using callback, if present
			using = function( props ) {
				var left = targetOffset.left - position.left,
					right = left + targetWidth - elemWidth,
					top = targetOffset.top - position.top,
					bottom = top + targetHeight - elemHeight,
					feedback = {
						target: {
							element: target,
							left: targetOffset.left,
							top: targetOffset.top,
							width: targetWidth,
							height: targetHeight
						},
						element: {
							element: elem,
							left: position.left,
							top: position.top,
							width: elemWidth,
							height: elemHeight
						},
						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
					};
				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
					feedback.horizontal = "center";
				}
				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
					feedback.vertical = "middle";
				}
				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
					feedback.important = "horizontal";
				} else {
					feedback.important = "vertical";
				}
				options.using.call( this, props, feedback );
			};
		}

		elem.offset( $.extend( position, { using: using } ) );
	});
};

$.ui.position = {
	fit: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
				outerWidth = within.width,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = withinOffset - collisionPosLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
				newOverRight;

			// element is wider than within
			if ( data.collisionWidth > outerWidth ) {
				// element is initially over the left side of within
				if ( overLeft > 0 && overRight <= 0 ) {
					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
					position.left += overLeft - newOverRight;
				// element is initially over right side of within
				} else if ( overRight > 0 && overLeft <= 0 ) {
					position.left = withinOffset;
				// element is initially over both left and right sides of within
				} else {
					if ( overLeft > overRight ) {
						position.left = withinOffset + outerWidth - data.collisionWidth;
					} else {
						position.left = withinOffset;
					}
				}
			// too far left -> align with left edge
			} else if ( overLeft > 0 ) {
				position.left += overLeft;
			// too far right -> align with right edge
			} else if ( overRight > 0 ) {
				position.left -= overRight;
			// adjust based on position and margin
			} else {
				position.left = max( position.left - collisionPosLeft, position.left );
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
				outerHeight = data.within.height,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = withinOffset - collisionPosTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
				newOverBottom;

			// element is taller than within
			if ( data.collisionHeight > outerHeight ) {
				// element is initially over the top of within
				if ( overTop > 0 && overBottom <= 0 ) {
					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
					position.top += overTop - newOverBottom;
				// element is initially over bottom of within
				} else if ( overBottom > 0 && overTop <= 0 ) {
					position.top = withinOffset;
				// element is initially over both top and bottom of within
				} else {
					if ( overTop > overBottom ) {
						position.top = withinOffset + outerHeight - data.collisionHeight;
					} else {
						position.top = withinOffset;
					}
				}
			// too far up -> align with top
			} else if ( overTop > 0 ) {
				position.top += overTop;
			// too far down -> align with bottom edge
			} else if ( overBottom > 0 ) {
				position.top -= overBottom;
			// adjust based on position and margin
			} else {
				position.top = max( position.top - collisionPosTop, position.top );
			}
		}
	},
	flip: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.left + within.scrollLeft,
				outerWidth = within.width,
				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = collisionPosLeft - offsetLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
				myOffset = data.my[ 0 ] === "left" ?
					-data.elemWidth :
					data.my[ 0 ] === "right" ?
						data.elemWidth :
						0,
				atOffset = data.at[ 0 ] === "left" ?
					data.targetWidth :
					data.at[ 0 ] === "right" ?
						-data.targetWidth :
						0,
				offset = -2 * data.offset[ 0 ],
				newOverRight,
				newOverLeft;

			if ( overLeft < 0 ) {
				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
					position.left += myOffset + atOffset + offset;
				}
			} else if ( overRight > 0 ) {
				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
					position.left += myOffset + atOffset + offset;
				}
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.top + within.scrollTop,
				outerHeight = within.height,
				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = collisionPosTop - offsetTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
				top = data.my[ 1 ] === "top",
				myOffset = top ?
					-data.elemHeight :
					data.my[ 1 ] === "bottom" ?
						data.elemHeight :
						0,
				atOffset = data.at[ 1 ] === "top" ?
					data.targetHeight :
					data.at[ 1 ] === "bottom" ?
						-data.targetHeight :
						0,
				offset = -2 * data.offset[ 1 ],
				newOverTop,
				newOverBottom;
			if ( overTop < 0 ) {
				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
				if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
					position.top += myOffset + atOffset + offset;
				}
			} else if ( overBottom > 0 ) {
				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
				if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
					position.top += myOffset + atOffset + offset;
				}
			}
		}
	},
	flipfit: {
		left: function() {
			$.ui.position.flip.left.apply( this, arguments );
			$.ui.position.fit.left.apply( this, arguments );
		},
		top: function() {
			$.ui.position.flip.top.apply( this, arguments );
			$.ui.position.fit.top.apply( this, arguments );
		}
	}
};

// fraction support test
(function() {
	var testElement, testElementParent, testElementStyle, offsetLeft, i,
		body = document.getElementsByTagName( "body" )[ 0 ],
		div = document.createElement( "div" );

	//Create a "fake body" for testing based on method used in jQuery.support
	testElement = document.createElement( body ? "div" : "body" );
	testElementStyle = {
		visibility: "hidden",
		width: 0,
		height: 0,
		border: 0,
		margin: 0,
		background: "none"
	};
	if ( body ) {
		$.extend( testElementStyle, {
			position: "absolute",
			left: "-1000px",
			top: "-1000px"
		});
	}
	for ( i in testElementStyle ) {
		testElement.style[ i ] = testElementStyle[ i ];
	}
	testElement.appendChild( div );
	testElementParent = body || document.documentElement;
	testElementParent.insertBefore( testElement, testElementParent.firstChild );

	div.style.cssText = "position: absolute; left: 10.7432222px;";

	offsetLeft = $( div ).offset().left;
	supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11;

	testElement.innerHTML = "";
	testElementParent.removeChild( testElement );
})();

})();

var position = $.ui.position;


/*!
 * jQuery UI Draggable 1.11.4
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/draggable/
 */


$.widget("ui.draggable", $.ui.mouse, {
	version: "1.11.4",
	widgetEventPrefix: "drag",
	options: {
		addClasses: true,
		appendTo: "parent",
		axis: false,
		connectToSortable: false,
		containment: false,
		cursor: "auto",
		cursorAt: false,
		grid: false,
		handle: false,
		helper: "original",
		iframeFix: false,
		opacity: false,
		refreshPositions: false,
		revert: false,
		revertDuration: 500,
		scope: "default",
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		snap: false,
		snapMode: "both",
		snapTolerance: 20,
		stack: false,
		zIndex: false,

		// callbacks
		drag: null,
		start: null,
		stop: null
	},
	_create: function() {

		if ( this.options.helper === "original" ) {
			this._setPositionRelative();
		}
		if (this.options.addClasses){
			this.element.addClass("ui-draggable");
		}
		if (this.options.disabled){
			this.element.addClass("ui-draggable-disabled");
		}
		this._setHandleClassName();

		this._mouseInit();
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "handle" ) {
			this._removeHandleClassName();
			this._setHandleClassName();
		}
	},

	_destroy: function() {
		if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
			this.destroyOnClear = true;
			return;
		}
		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
		this._removeHandleClassName();
		this._mouseDestroy();
	},

	_mouseCapture: function(event) {
		var o = this.options;

		this._blurActiveElement( event );

		// among others, prevent a drag on a resizable-handle
		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
			return false;
		}

		//Quit if we're not on a valid handle
		this.handle = this._getHandle(event);
		if (!this.handle) {
			return false;
		}

		this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );

		return true;

	},

	_blockFrames: function( selector ) {
		this.iframeBlocks = this.document.find( selector ).map(function() {
			var iframe = $( this );

			return $( "<div>" )
				.css( "position", "absolute" )
				.appendTo( iframe.parent() )
				.outerWidth( iframe.outerWidth() )
				.outerHeight( iframe.outerHeight() )
				.offset( iframe.offset() )[ 0 ];
		});
	},

	_unblockFrames: function() {
		if ( this.iframeBlocks ) {
			this.iframeBlocks.remove();
			delete this.iframeBlocks;
		}
	},

	_blurActiveElement: function( event ) {
		var document = this.document[ 0 ];

		// Only need to blur if the event occurred on the draggable itself, see #10527
		if ( !this.handleElement.is( event.target ) ) {
			return;
		}

		// support: IE9
		// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
		try {

			// Support: IE9, IE10
			// If the <body> is blurred, IE will switch windows, see #9520
			if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== "body" ) {

				// Blur any element that currently has focus, see #4261
				$( document.activeElement ).blur();
			}
		} catch ( error ) {}
	},

	_mouseStart: function(event) {

		var o = this.options;

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		this.helper.addClass("ui-draggable-dragging");

		//Cache the helper size
		this._cacheHelperProportions();

		//If ddmanager is used for droppables, set the global draggable
		if ($.ui.ddmanager) {
			$.ui.ddmanager.current = this;
		}

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Store the helper's css position
		this.cssPosition = this.helper.css( "position" );
		this.scrollParent = this.helper.scrollParent( true );
		this.offsetParent = this.helper.offsetParent();
		this.hasFixedAncestor = this.helper.parents().filter(function() {
				return $( this ).css( "position" ) === "fixed";
			}).length > 0;

		//The element's absolute position on the page minus margins
		this.positionAbs = this.element.offset();
		this._refreshOffsets( event );

		//Generate the original position
		this.originalPosition = this.position = this._generatePosition( event, false );
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Set a containment if given in the options
		this._setContainment();

		//Trigger event + callbacks
		if (this._trigger("start", event) === false) {
			this._clear();
			return false;
		}

		//Recache the helper size
		this._cacheHelperProportions();

		//Prepare the droppable offsets
		if ($.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(this, event);
		}

		// Reset helper's right/bottom css if they're set and set explicit width/height instead
		// as this prevents resizing of elements with right/bottom set (see #7772)
		this._normalizeRightBottom();

		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position

		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
		if ( $.ui.ddmanager ) {
			$.ui.ddmanager.dragStart(this, event);
		}

		return true;
	},

	_refreshOffsets: function( event ) {
		this.offset = {
			top: this.positionAbs.top - this.margins.top,
			left: this.positionAbs.left - this.margins.left,
			scroll: false,
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset()
		};

		this.offset.click = {
			left: event.pageX - this.offset.left,
			top: event.pageY - this.offset.top
		};
	},

	_mouseDrag: function(event, noPropagation) {
		// reset any necessary cached properties (see #5009)
		if ( this.hasFixedAncestor ) {
			this.offset.parent = this._getParentOffset();
		}

		//Compute the helpers position
		this.position = this._generatePosition( event, true );
		this.positionAbs = this._convertPositionTo("absolute");

		//Call plugins and callbacks and use the resulting position if something is returned
		if (!noPropagation) {
			var ui = this._uiHash();
			if (this._trigger("drag", event, ui) === false) {
				this._mouseUp({});
				return false;
			}
			this.position = ui.position;
		}

		this.helper[ 0 ].style.left = this.position.left + "px";
		this.helper[ 0 ].style.top = this.position.top + "px";

		if ($.ui.ddmanager) {
			$.ui.ddmanager.drag(this, event);
		}

		return false;
	},

	_mouseStop: function(event) {

		//If we are using droppables, inform the manager about the drop
		var that = this,
			dropped = false;
		if ($.ui.ddmanager && !this.options.dropBehaviour) {
			dropped = $.ui.ddmanager.drop(this, event);
		}

		//if a drop comes from outside (a sortable)
		if (this.dropped) {
			dropped = this.dropped;
			this.dropped = false;
		}

		if ((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
				if (that._trigger("stop", event) !== false) {
					that._clear();
				}
			});
		} else {
			if (this._trigger("stop", event) !== false) {
				this._clear();
			}
		}

		return false;
	},

	_mouseUp: function( event ) {
		this._unblockFrames();

		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
		if ( $.ui.ddmanager ) {
			$.ui.ddmanager.dragStop(this, event);
		}

		// Only need to focus if the event occurred on the draggable itself, see #10527
		if ( this.handleElement.is( event.target ) ) {
			// The interaction is over; whether or not the click resulted in a drag, focus the element
			this.element.focus();
		}

		return $.ui.mouse.prototype._mouseUp.call(this, event);
	},

	cancel: function() {

		if (this.helper.is(".ui-draggable-dragging")) {
			this._mouseUp({});
		} else {
			this._clear();
		}

		return this;

	},

	_getHandle: function(event) {
		return this.options.handle ?
			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
			true;
	},

	_setHandleClassName: function() {
		this.handleElement = this.options.handle ?
			this.element.find( this.options.handle ) : this.element;
		this.handleElement.addClass( "ui-draggable-handle" );
	},

	_removeHandleClassName: function() {
		this.handleElement.removeClass( "ui-draggable-handle" );
	},

	_createHelper: function(event) {

		var o = this.options,
			helperIsFunction = $.isFunction( o.helper ),
			helper = helperIsFunction ?
				$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
				( o.helper === "clone" ?
					this.element.clone().removeAttr( "id" ) :
					this.element );

		if (!helper.parents("body").length) {
			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
		}

		// http://bugs.jqueryui.com/ticket/9446
		// a helper function can return the original element
		// which wouldn't have been set to relative in _create
		if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
			this._setPositionRelative();
		}

		if (helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
			helper.css("position", "absolute");
		}

		return helper;

	},

	_setPositionRelative: function() {
		if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
			this.element[ 0 ].style.position = "relative";
		}
	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj === "string") {
			obj = obj.split(" ");
		}
		if ($.isArray(obj)) {
			obj = { left: +obj[0], top: +obj[1] || 0 };
		}
		if ("left" in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ("right" in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ("top" in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ("bottom" in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_isRootNode: function( element ) {
		return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
	},

	_getParentOffset: function() {

		//Get the offsetParent and cache its position
		var po = this.offsetParent.offset(),
			document = this.document[ 0 ];

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if (this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
			po = { top: 0, left: 0 };
		}

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"), 10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"), 10) || 0)
		};

	},

	_getRelativeOffset: function() {
		if ( this.cssPosition !== "relative" ) {
			return { top: 0, left: 0 };
		}

		var p = this.element.position(),
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );

		return {
			top: p.top - ( parseInt(this.helper.css( "top" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
			left: p.left - ( parseInt(this.helper.css( "left" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
		};

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.element.css("marginLeft"), 10) || 0),
			top: (parseInt(this.element.css("marginTop"), 10) || 0),
			right: (parseInt(this.element.css("marginRight"), 10) || 0),
			bottom: (parseInt(this.element.css("marginBottom"), 10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var isUserScrollable, c, ce,
			o = this.options,
			document = this.document[ 0 ];

		this.relativeContainer = null;

		if ( !o.containment ) {
			this.containment = null;
			return;
		}

		if ( o.containment === "window" ) {
			this.containment = [
				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
			];
			return;
		}

		if ( o.containment === "document") {
			this.containment = [
				0,
				0,
				$( document ).width() - this.helperProportions.width - this.margins.left,
				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
			];
			return;
		}

		if ( o.containment.constructor === Array ) {
			this.containment = o.containment;
			return;
		}

		if ( o.containment === "parent" ) {
			o.containment = this.helper[ 0 ].parentNode;
		}

		c = $( o.containment );
		ce = c[ 0 ];

		if ( !ce ) {
			return;
		}

		isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );

		this.containment = [
			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
			( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
				( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
				( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
				this.helperProportions.width -
				this.margins.left -
				this.margins.right,
			( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
				( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
				( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
				this.helperProportions.height -
				this.margins.top -
				this.margins.bottom
		];
		this.relativeContainer = c;
	},

	_convertPositionTo: function(d, pos) {

		if (!pos) {
			pos = this.position;
		}

		var mod = d === "absolute" ? 1 : -1,
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );

		return {
			top: (
				pos.top	+																// The absolute mouse position
				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod)
			),
			left: (
				pos.left +																// The absolute mouse position
				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod)
			)
		};

	},

	_generatePosition: function( event, constrainPosition ) {

		var containment, co, top, left,
			o = this.options,
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
			pageX = event.pageX,
			pageY = event.pageY;

		// Cache the scroll
		if ( !scrollIsRootNode || !this.offset.scroll ) {
			this.offset.scroll = {
				top: this.scrollParent.scrollTop(),
				left: this.scrollParent.scrollLeft()
			};
		}

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		// If we are not dragging yet, we won't check for options
		if ( constrainPosition ) {
			if ( this.containment ) {
				if ( this.relativeContainer ){
					co = this.relativeContainer.offset();
					containment = [
						this.containment[ 0 ] + co.left,
						this.containment[ 1 ] + co.top,
						this.containment[ 2 ] + co.left,
						this.containment[ 3 ] + co.top
					];
				} else {
					containment = this.containment;
				}

				if (event.pageX - this.offset.click.left < containment[0]) {
					pageX = containment[0] + this.offset.click.left;
				}
				if (event.pageY - this.offset.click.top < containment[1]) {
					pageY = containment[1] + this.offset.click.top;
				}
				if (event.pageX - this.offset.click.left > containment[2]) {
					pageX = containment[2] + this.offset.click.left;
				}
				if (event.pageY - this.offset.click.top > containment[3]) {
					pageY = containment[3] + this.offset.click.top;
				}
			}

			if (o.grid) {
				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

			if ( o.axis === "y" ) {
				pageX = this.originalPageX;
			}

			if ( o.axis === "x" ) {
				pageY = this.originalPageY;
			}
		}

		return {
			top: (
				pageY -																	// The absolute mouse position
				this.offset.click.top	-												// Click offset (relative to the element)
				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
				( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
			),
			left: (
				pageX -																	// The absolute mouse position
				this.offset.click.left -												// Click offset (relative to the element)
				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
				( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
			)
		};

	},

	_clear: function() {
		this.helper.removeClass("ui-draggable-dragging");
		if (this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
			this.helper.remove();
		}
		this.helper = null;
		this.cancelHelperRemoval = false;
		if ( this.destroyOnClear ) {
			this.destroy();
		}
	},

	_normalizeRightBottom: function() {
		if ( this.options.axis !== "y" && this.helper.css( "right" ) !== "auto" ) {
			this.helper.width( this.helper.width() );
			this.helper.css( "right", "auto" );
		}
		if ( this.options.axis !== "x" && this.helper.css( "bottom" ) !== "auto" ) {
			this.helper.height( this.helper.height() );
			this.helper.css( "bottom", "auto" );
		}
	},

	// From now on bulk stuff - mainly helpers

	_trigger: function( type, event, ui ) {
		ui = ui || this._uiHash();
		$.ui.plugin.call( this, type, [ event, ui, this ], true );

		// Absolute position and offset (see #6884 ) have to be recalculated after plugins
		if ( /^(drag|start|stop)/.test( type ) ) {
			this.positionAbs = this._convertPositionTo( "absolute" );
			ui.offset = this.positionAbs;
		}
		return $.Widget.prototype._trigger.call( this, type, event, ui );
	},

	plugins: {},

	_uiHash: function() {
		return {
			helper: this.helper,
			position: this.position,
			originalPosition: this.originalPosition,
			offset: this.positionAbs
		};
	}

});

$.ui.plugin.add( "draggable", "connectToSortable", {
	start: function( event, ui, draggable ) {
		var uiSortable = $.extend( {}, ui, {
			item: draggable.element
		});

		draggable.sortables = [];
		$( draggable.options.connectToSortable ).each(function() {
			var sortable = $( this ).sortable( "instance" );

			if ( sortable && !sortable.options.disabled ) {
				draggable.sortables.push( sortable );

				// refreshPositions is called at drag start to refresh the containerCache
				// which is used in drag. This ensures it's initialized and synchronized
				// with any changes that might have happened on the page since initialization.
				sortable.refreshPositions();
				sortable._trigger("activate", event, uiSortable);
			}
		});
	},
	stop: function( event, ui, draggable ) {
		var uiSortable = $.extend( {}, ui, {
			item: draggable.element
		});

		draggable.cancelHelperRemoval = false;

		$.each( draggable.sortables, function() {
			var sortable = this;

			if ( sortable.isOver ) {
				sortable.isOver = 0;

				// Allow this sortable to handle removing the helper
				draggable.cancelHelperRemoval = true;
				sortable.cancelHelperRemoval = false;

				// Use _storedCSS To restore properties in the sortable,
				// as this also handles revert (#9675) since the draggable
				// may have modified them in unexpected ways (#8809)
				sortable._storedCSS = {
					position: sortable.placeholder.css( "position" ),
					top: sortable.placeholder.css( "top" ),
					left: sortable.placeholder.css( "left" )
				};

				sortable._mouseStop(event);

				// Once drag has ended, the sortable should return to using
				// its original helper, not the shared helper from draggable
				sortable.options.helper = sortable.options._helper;
			} else {
				// Prevent this Sortable from removing the helper.
				// However, don't set the draggable to remove the helper
				// either as another connected Sortable may yet handle the removal.
				sortable.cancelHelperRemoval = true;

				sortable._trigger( "deactivate", event, uiSortable );
			}
		});
	},
	drag: function( event, ui, draggable ) {
		$.each( draggable.sortables, function() {
			var innermostIntersecting = false,
				sortable = this;

			// Copy over variables that sortable's _intersectsWith uses
			sortable.positionAbs = draggable.positionAbs;
			sortable.helperProportions = draggable.helperProportions;
			sortable.offset.click = draggable.offset.click;

			if ( sortable._intersectsWith( sortable.containerCache ) ) {
				innermostIntersecting = true;

				$.each( draggable.sortables, function() {
					// Copy over variables that sortable's _intersectsWith uses
					this.positionAbs = draggable.positionAbs;
					this.helperProportions = draggable.helperProportions;
					this.offset.click = draggable.offset.click;

					if ( this !== sortable &&
							this._intersectsWith( this.containerCache ) &&
							$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
						innermostIntersecting = false;
					}

					return innermostIntersecting;
				});
			}

			if ( innermostIntersecting ) {
				// If it intersects, we use a little isOver variable and set it once,
				// so that the move-in stuff gets fired only once.
				if ( !sortable.isOver ) {
					sortable.isOver = 1;

					// Store draggable's parent in case we need to reappend to it later.
					draggable._parent = ui.helper.parent();

					sortable.currentItem = ui.helper
						.appendTo( sortable.element )
						.data( "ui-sortable-item", true );

					// Store helper option to later restore it
					sortable.options._helper = sortable.options.helper;

					sortable.options.helper = function() {
						return ui.helper[ 0 ];
					};

					// Fire the start events of the sortable with our passed browser event,
					// and our own helper (so it doesn't create a new one)
					event.target = sortable.currentItem[ 0 ];
					sortable._mouseCapture( event, true );
					sortable._mouseStart( event, true, true );

					// Because the browser event is way off the new appended portlet,
					// modify necessary variables to reflect the changes
					sortable.offset.click.top = draggable.offset.click.top;
					sortable.offset.click.left = draggable.offset.click.left;
					sortable.offset.parent.left -= draggable.offset.parent.left -
						sortable.offset.parent.left;
					sortable.offset.parent.top -= draggable.offset.parent.top -
						sortable.offset.parent.top;

					draggable._trigger( "toSortable", event );

					// Inform draggable that the helper is in a valid drop zone,
					// used solely in the revert option to handle "valid/invalid".
					draggable.dropped = sortable.element;

					// Need to refreshPositions of all sortables in the case that
					// adding to one sortable changes the location of the other sortables (#9675)
					$.each( draggable.sortables, function() {
						this.refreshPositions();
					});

					// hack so receive/update callbacks work (mostly)
					draggable.currentItem = draggable.element;
					sortable.fromOutside = draggable;
				}

				if ( sortable.currentItem ) {
					sortable._mouseDrag( event );
					// Copy the sortable's position because the draggable's can potentially reflect
					// a relative position, while sortable is always absolute, which the dragged
					// element has now become. (#8809)
					ui.position = sortable.position;
				}
			} else {
				// If it doesn't intersect with the sortable, and it intersected before,
				// we fake the drag stop of the sortable, but make sure it doesn't remove
				// the helper by using cancelHelperRemoval.
				if ( sortable.isOver ) {

					sortable.isOver = 0;
					sortable.cancelHelperRemoval = true;

					// Calling sortable's mouseStop would trigger a revert,
					// so revert must be temporarily false until after mouseStop is called.
					sortable.options._revert = sortable.options.revert;
					sortable.options.revert = false;

					sortable._trigger( "out", event, sortable._uiHash( sortable ) );
					sortable._mouseStop( event, true );

					// restore sortable behaviors that were modfied
					// when the draggable entered the sortable area (#9481)
					sortable.options.revert = sortable.options._revert;
					sortable.options.helper = sortable.options._helper;

					if ( sortable.placeholder ) {
						sortable.placeholder.remove();
					}

					// Restore and recalculate the draggable's offset considering the sortable
					// may have modified them in unexpected ways. (#8809, #10669)
					ui.helper.appendTo( draggable._parent );
					draggable._refreshOffsets( event );
					ui.position = draggable._generatePosition( event, true );

					draggable._trigger( "fromSortable", event );

					// Inform draggable that the helper is no longer in a valid drop zone
					draggable.dropped = false;

					// Need to refreshPositions of all sortables just in case removing
					// from one sortable changes the location of other sortables (#9675)
					$.each( draggable.sortables, function() {
						this.refreshPositions();
					});
				}
			}
		});
	}
});

$.ui.plugin.add("draggable", "cursor", {
	start: function( event, ui, instance ) {
		var t = $( "body" ),
			o = instance.options;

		if (t.css("cursor")) {
			o._cursor = t.css("cursor");
		}
		t.css("cursor", o.cursor);
	},
	stop: function( event, ui, instance ) {
		var o = instance.options;
		if (o._cursor) {
			$("body").css("cursor", o._cursor);
		}
	}
});

$.ui.plugin.add("draggable", "opacity", {
	start: function( event, ui, instance ) {
		var t = $( ui.helper ),
			o = instance.options;
		if (t.css("opacity")) {
			o._opacity = t.css("opacity");
		}
		t.css("opacity", o.opacity);
	},
	stop: function( event, ui, instance ) {
		var o = instance.options;
		if (o._opacity) {
			$(ui.helper).css("opacity", o._opacity);
		}
	}
});

$.ui.plugin.add("draggable", "scroll", {
	start: function( event, ui, i ) {
		if ( !i.scrollParentNotHidden ) {
			i.scrollParentNotHidden = i.helper.scrollParent( false );
		}

		if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
			i.overflowOffset = i.scrollParentNotHidden.offset();
		}
	},
	drag: function( event, ui, i  ) {

		var o = i.options,
			scrolled = false,
			scrollParent = i.scrollParentNotHidden[ 0 ],
			document = i.document[ 0 ];

		if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
			if ( !o.axis || o.axis !== "x" ) {
				if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < o.scrollSensitivity ) {
					scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
				} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
					scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
				}
			}

			if ( !o.axis || o.axis !== "y" ) {
				if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < o.scrollSensitivity ) {
					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
				} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
				}
			}

		} else {

			if (!o.axis || o.axis !== "x") {
				if (event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				} else if ($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
				}
			}

			if (!o.axis || o.axis !== "y") {
				if (event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				} else if ($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
				}
			}

		}

		if (scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(i, event);
		}

	}
});

$.ui.plugin.add("draggable", "snap", {
	start: function( event, ui, i ) {

		var o = i.options;

		i.snapElements = [];

		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
			var $t = $(this),
				$o = $t.offset();
			if (this !== i.element[0]) {
				i.snapElements.push({
					item: this,
					width: $t.outerWidth(), height: $t.outerHeight(),
					top: $o.top, left: $o.left
				});
			}
		});

	},
	drag: function( event, ui, inst ) {

		var ts, bs, ls, rs, l, r, t, b, i, first,
			o = inst.options,
			d = o.snapTolerance,
			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;

		for (i = inst.snapElements.length - 1; i >= 0; i--){

			l = inst.snapElements[i].left - inst.margins.left;
			r = l + inst.snapElements[i].width;
			t = inst.snapElements[i].top - inst.margins.top;
			b = t + inst.snapElements[i].height;

			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
				if (inst.snapElements[i].snapping) {
					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
				}
				inst.snapElements[i].snapping = false;
				continue;
			}

			if (o.snapMode !== "inner") {
				ts = Math.abs(t - y2) <= d;
				bs = Math.abs(b - y1) <= d;
				ls = Math.abs(l - x2) <= d;
				rs = Math.abs(r - x1) <= d;
				if (ts) {
					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top;
				}
				if (bs) {
					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top;
				}
				if (ls) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left;
				}
				if (rs) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left;
				}
			}

			first = (ts || bs || ls || rs);

			if (o.snapMode !== "outer") {
				ts = Math.abs(t - y1) <= d;
				bs = Math.abs(b - y2) <= d;
				ls = Math.abs(l - x1) <= d;
				rs = Math.abs(r - x2) <= d;
				if (ts) {
					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top;
				}
				if (bs) {
					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top;
				}
				if (ls) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left;
				}
				if (rs) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left;
				}
			}

			if (!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
			}
			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);

		}

	}
});

$.ui.plugin.add("draggable", "stack", {
	start: function( event, ui, instance ) {
		var min,
			o = instance.options,
			group = $.makeArray($(o.stack)).sort(function(a, b) {
				return (parseInt($(a).css("zIndex"), 10) || 0) - (parseInt($(b).css("zIndex"), 10) || 0);
			});

		if (!group.length) { return; }

		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
		$(group).each(function(i) {
			$(this).css("zIndex", min + i);
		});
		this.css("zIndex", (min + group.length));
	}
});

$.ui.plugin.add("draggable", "zIndex", {
	start: function( event, ui, instance ) {
		var t = $( ui.helper ),
			o = instance.options;

		if (t.css("zIndex")) {
			o._zIndex = t.css("zIndex");
		}
		t.css("zIndex", o.zIndex);
	},
	stop: function( event, ui, instance ) {
		var o = instance.options;

		if (o._zIndex) {
			$(ui.helper).css("zIndex", o._zIndex);
		}
	}
});

var draggable = $.ui.draggable;


/*!
 * jQuery UI Resizable 1.11.4
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/resizable/
 */


$.widget("ui.resizable", $.ui.mouse, {
	version: "1.11.4",
	widgetEventPrefix: "resize",
	options: {
		alsoResize: false,
		animate: false,
		animateDuration: "slow",
		animateEasing: "swing",
		aspectRatio: false,
		autoHide: false,
		containment: false,
		ghost: false,
		grid: false,
		handles: "e,s,se",
		helper: false,
		maxHeight: null,
		maxWidth: null,
		minHeight: 10,
		minWidth: 10,
		// See #7960
		zIndex: 90,

		// callbacks
		resize: null,
		start: null,
		stop: null
	},

	_num: function( value ) {
		return parseInt( value, 10 ) || 0;
	},

	_isNumber: function( value ) {
		return !isNaN( parseInt( value, 10 ) );
	},

	_hasScroll: function( el, a ) {

		if ( $( el ).css( "overflow" ) === "hidden") {
			return false;
		}

		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
			has = false;

		if ( el[ scroll ] > 0 ) {
			return true;
		}

		// TODO: determine which cases actually cause this to happen
		// if the element doesn't have the scroll set, see if it's possible to
		// set the scroll
		el[ scroll ] = 1;
		has = ( el[ scroll ] > 0 );
		el[ scroll ] = 0;
		return has;
	},

	_create: function() {

		var n, i, handle, axis, hname,
			that = this,
			o = this.options;
		this.element.addClass("ui-resizable");

		$.extend(this, {
			_aspectRatio: !!(o.aspectRatio),
			aspectRatio: o.aspectRatio,
			originalElement: this.element,
			_proportionallyResizeElements: [],
			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
		});

		// Wrap the element if it cannot hold child nodes
		if (this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)) {

			this.element.wrap(
				$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
					position: this.element.css("position"),
					width: this.element.outerWidth(),
					height: this.element.outerHeight(),
					top: this.element.css("top"),
					left: this.element.css("left")
				})
			);

			this.element = this.element.parent().data(
				"ui-resizable", this.element.resizable( "instance" )
			);

			this.elementIsWrapper = true;

			this.element.css({
				marginLeft: this.originalElement.css("marginLeft"),
				marginTop: this.originalElement.css("marginTop"),
				marginRight: this.originalElement.css("marginRight"),
				marginBottom: this.originalElement.css("marginBottom")
			});
			this.originalElement.css({
				marginLeft: 0,
				marginTop: 0,
				marginRight: 0,
				marginBottom: 0
			});
			// support: Safari
			// Prevent Safari textarea resize
			this.originalResizeStyle = this.originalElement.css("resize");
			this.originalElement.css("resize", "none");

			this._proportionallyResizeElements.push( this.originalElement.css({
				position: "static",
				zoom: 1,
				display: "block"
			}) );

			// support: IE9
			// avoid IE jump (hard set the margin)
			this.originalElement.css({ margin: this.originalElement.css("margin") });

			this._proportionallyResize();
		}

		this.handles = o.handles ||
			( !$(".ui-resizable-handle", this.element).length ?
				"e,s,se" : {
					n: ".ui-resizable-n",
					e: ".ui-resizable-e",
					s: ".ui-resizable-s",
					w: ".ui-resizable-w",
					se: ".ui-resizable-se",
					sw: ".ui-resizable-sw",
					ne: ".ui-resizable-ne",
					nw: ".ui-resizable-nw"
				} );

		this._handles = $();
		if ( this.handles.constructor === String ) {

			if ( this.handles === "all") {
				this.handles = "n,e,s,w,se,sw,ne,nw";
			}

			n = this.handles.split(",");
			this.handles = {};

			for (i = 0; i < n.length; i++) {

				handle = $.trim(n[i]);
				hname = "ui-resizable-" + handle;
				axis = $("<div class='ui-resizable-handle " + hname + "'></div>");

				axis.css({ zIndex: o.zIndex });

				// TODO : What's going on here?
				if ("se" === handle) {
					axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
				}

				this.handles[handle] = ".ui-resizable-" + handle;
				this.element.append(axis);
			}

		}

		this._renderAxis = function(target) {

			var i, axis, padPos, padWrapper;

			target = target || this.element;

			for (i in this.handles) {

				if (this.handles[i].constructor === String) {
					this.handles[i] = this.element.children( this.handles[ i ] ).first().show();
				} else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) {
					this.handles[ i ] = $( this.handles[ i ] );
					this._on( this.handles[ i ], { "mousedown": that._mouseDown });
				}

				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)) {

					axis = $(this.handles[i], this.element);

					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();

					padPos = [ "padding",
						/ne|nw|n/.test(i) ? "Top" :
						/se|sw|s/.test(i) ? "Bottom" :
						/^e$/.test(i) ? "Right" : "Left" ].join("");

					target.css(padPos, padWrapper);

					this._proportionallyResize();
				}

				this._handles = this._handles.add( this.handles[ i ] );
			}
		};

		// TODO: make renderAxis a prototype function
		this._renderAxis(this.element);

		this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) );
		this._handles.disableSelection();

		this._handles.mouseover(function() {
			if (!that.resizing) {
				if (this.className) {
					axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
				}
				that.axis = axis && axis[1] ? axis[1] : "se";
			}
		});

		if (o.autoHide) {
			this._handles.hide();
			$(this.element)
				.addClass("ui-resizable-autohide")
				.mouseenter(function() {
					if (o.disabled) {
						return;
					}
					$(this).removeClass("ui-resizable-autohide");
					that._handles.show();
				})
				.mouseleave(function() {
					if (o.disabled) {
						return;
					}
					if (!that.resizing) {
						$(this).addClass("ui-resizable-autohide");
						that._handles.hide();
					}
				});
		}

		this._mouseInit();
	},

	_destroy: function() {

		this._mouseDestroy();

		var wrapper,
			_destroy = function(exp) {
				$(exp)
					.removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
					.removeData("resizable")
					.removeData("ui-resizable")
					.unbind(".resizable")
					.find(".ui-resizable-handle")
						.remove();
			};

		// TODO: Unwrap at same DOM position
		if (this.elementIsWrapper) {
			_destroy(this.element);
			wrapper = this.element;
			this.originalElement.css({
				position: wrapper.css("position"),
				width: wrapper.outerWidth(),
				height: wrapper.outerHeight(),
				top: wrapper.css("top"),
				left: wrapper.css("left")
			}).insertAfter( wrapper );
			wrapper.remove();
		}

		this.originalElement.css("resize", this.originalResizeStyle);
		_destroy(this.originalElement);

		return this;
	},

	_mouseCapture: function(event) {
		var i, handle,
			capture = false;

		for (i in this.handles) {
			handle = $(this.handles[i])[0];
			if (handle === event.target || $.contains(handle, event.target)) {
				capture = true;
			}
		}

		return !this.options.disabled && capture;
	},

	_mouseStart: function(event) {

		var curleft, curtop, cursor,
			o = this.options,
			el = this.element;

		this.resizing = true;

		this._renderProxy();

		curleft = this._num(this.helper.css("left"));
		curtop = this._num(this.helper.css("top"));

		if (o.containment) {
			curleft += $(o.containment).scrollLeft() || 0;
			curtop += $(o.containment).scrollTop() || 0;
		}

		this.offset = this.helper.offset();
		this.position = { left: curleft, top: curtop };

		this.size = this._helper ? {
				width: this.helper.width(),
				height: this.helper.height()
			} : {
				width: el.width(),
				height: el.height()
			};

		this.originalSize = this._helper ? {
				width: el.outerWidth(),
				height: el.outerHeight()
			} : {
				width: el.width(),
				height: el.height()
			};

		this.sizeDiff = {
			width: el.outerWidth() - el.width(),
			height: el.outerHeight() - el.height()
		};

		this.originalPosition = { left: curleft, top: curtop };
		this.originalMousePosition = { left: event.pageX, top: event.pageY };

		this.aspectRatio = (typeof o.aspectRatio === "number") ?
			o.aspectRatio :
			((this.originalSize.width / this.originalSize.height) || 1);

		cursor = $(".ui-resizable-" + this.axis).css("cursor");
		$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);

		el.addClass("ui-resizable-resizing");
		this._propagate("start", event);
		return true;
	},

	_mouseDrag: function(event) {

		var data, props,
			smp = this.originalMousePosition,
			a = this.axis,
			dx = (event.pageX - smp.left) || 0,
			dy = (event.pageY - smp.top) || 0,
			trigger = this._change[a];

		this._updatePrevProperties();

		if (!trigger) {
			return false;
		}

		data = trigger.apply(this, [ event, dx, dy ]);

		this._updateVirtualBoundaries(event.shiftKey);
		if (this._aspectRatio || event.shiftKey) {
			data = this._updateRatio(data, event);
		}

		data = this._respectSize(data, event);

		this._updateCache(data);

		this._propagate("resize", event);

		props = this._applyChanges();

		if ( !this._helper && this._proportionallyResizeElements.length ) {
			this._proportionallyResize();
		}

		if ( !$.isEmptyObject( props ) ) {
			this._updatePrevProperties();
			this._trigger( "resize", event, this.ui() );
			this._applyChanges();
		}

		return false;
	},

	_mouseStop: function(event) {

		this.resizing = false;
		var pr, ista, soffseth, soffsetw, s, left, top,
			o = this.options, that = this;

		if (this._helper) {

			pr = this._proportionallyResizeElements;
			ista = pr.length && (/textarea/i).test(pr[0].nodeName);
			soffseth = ista && this._hasScroll(pr[0], "left") ? 0 : that.sizeDiff.height;
			soffsetw = ista ? 0 : that.sizeDiff.width;

			s = {
				width: (that.helper.width()  - soffsetw),
				height: (that.helper.height() - soffseth)
			};
			left = (parseInt(that.element.css("left"), 10) +
				(that.position.left - that.originalPosition.left)) || null;
			top = (parseInt(that.element.css("top"), 10) +
				(that.position.top - that.originalPosition.top)) || null;

			if (!o.animate) {
				this.element.css($.extend(s, { top: top, left: left }));
			}

			that.helper.height(that.size.height);
			that.helper.width(that.size.width);

			if (this._helper && !o.animate) {
				this._proportionallyResize();
			}
		}

		$("body").css("cursor", "auto");

		this.element.removeClass("ui-resizable-resizing");

		this._propagate("stop", event);

		if (this._helper) {
			this.helper.remove();
		}

		return false;

	},

	_updatePrevProperties: function() {
		this.prevPosition = {
			top: this.position.top,
			left: this.position.left
		};
		this.prevSize = {
			width: this.size.width,
			height: this.size.height
		};
	},

	_applyChanges: function() {
		var props = {};

		if ( this.position.top !== this.prevPosition.top ) {
			props.top = this.position.top + "px";
		}
		if ( this.position.left !== this.prevPosition.left ) {
			props.left = this.position.left + "px";
		}
		if ( this.size.width !== this.prevSize.width ) {
			props.width = this.size.width + "px";
		}
		if ( this.size.height !== this.prevSize.height ) {
			props.height = this.size.height + "px";
		}

		this.helper.css( props );

		return props;
	},

	_updateVirtualBoundaries: function(forceAspectRatio) {
		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
			o = this.options;

		b = {
			minWidth: this._isNumber(o.minWidth) ? o.minWidth : 0,
			maxWidth: this._isNumber(o.maxWidth) ? o.maxWidth : Infinity,
			minHeight: this._isNumber(o.minHeight) ? o.minHeight : 0,
			maxHeight: this._isNumber(o.maxHeight) ? o.maxHeight : Infinity
		};

		if (this._aspectRatio || forceAspectRatio) {
			pMinWidth = b.minHeight * this.aspectRatio;
			pMinHeight = b.minWidth / this.aspectRatio;
			pMaxWidth = b.maxHeight * this.aspectRatio;
			pMaxHeight = b.maxWidth / this.aspectRatio;

			if (pMinWidth > b.minWidth) {
				b.minWidth = pMinWidth;
			}
			if (pMinHeight > b.minHeight) {
				b.minHeight = pMinHeight;
			}
			if (pMaxWidth < b.maxWidth) {
				b.maxWidth = pMaxWidth;
			}
			if (pMaxHeight < b.maxHeight) {
				b.maxHeight = pMaxHeight;
			}
		}
		this._vBoundaries = b;
	},

	_updateCache: function(data) {
		this.offset = this.helper.offset();
		if (this._isNumber(data.left)) {
			this.position.left = data.left;
		}
		if (this._isNumber(data.top)) {
			this.position.top = data.top;
		}
		if (this._isNumber(data.height)) {
			this.size.height = data.height;
		}
		if (this._isNumber(data.width)) {
			this.size.width = data.width;
		}
	},

	_updateRatio: function( data ) {

		var cpos = this.position,
			csize = this.size,
			a = this.axis;

		if (this._isNumber(data.height)) {
			data.width = (data.height * this.aspectRatio);
		} else if (this._isNumber(data.width)) {
			data.height = (data.width / this.aspectRatio);
		}

		if (a === "sw") {
			data.left = cpos.left + (csize.width - data.width);
			data.top = null;
		}
		if (a === "nw") {
			data.top = cpos.top + (csize.height - data.height);
			data.left = cpos.left + (csize.width - data.width);
		}

		return data;
	},

	_respectSize: function( data ) {

		var o = this._vBoundaries,
			a = this.axis,
			ismaxw = this._isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width),
			ismaxh = this._isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
			isminw = this._isNumber(data.width) && o.minWidth && (o.minWidth > data.width),
			isminh = this._isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
			dw = this.originalPosition.left + this.originalSize.width,
			dh = this.position.top + this.size.height,
			cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
		if (isminw) {
			data.width = o.minWidth;
		}
		if (isminh) {
			data.height = o.minHeight;
		}
		if (ismaxw) {
			data.width = o.maxWidth;
		}
		if (ismaxh) {
			data.height = o.maxHeight;
		}

		if (isminw && cw) {
			data.left = dw - o.minWidth;
		}
		if (ismaxw && cw) {
			data.left = dw - o.maxWidth;
		}
		if (isminh && ch) {
			data.top = dh - o.minHeight;
		}
		if (ismaxh && ch) {
			data.top = dh - o.maxHeight;
		}

		// Fixing jump error on top/left - bug #2330
		if (!data.width && !data.height && !data.left && data.top) {
			data.top = null;
		} else if (!data.width && !data.height && !data.top && data.left) {
			data.left = null;
		}

		return data;
	},

	_getPaddingPlusBorderDimensions: function( element ) {
		var i = 0,
			widths = [],
			borders = [
				element.css( "borderTopWidth" ),
				element.css( "borderRightWidth" ),
				element.css( "borderBottomWidth" ),
				element.css( "borderLeftWidth" )
			],
			paddings = [
				element.css( "paddingTop" ),
				element.css( "paddingRight" ),
				element.css( "paddingBottom" ),
				element.css( "paddingLeft" )
			];

		for ( ; i < 4; i++ ) {
			widths[ i ] = ( parseInt( borders[ i ], 10 ) || 0 );
			widths[ i ] += ( parseInt( paddings[ i ], 10 ) || 0 );
		}

		return {
			height: widths[ 0 ] + widths[ 2 ],
			width: widths[ 1 ] + widths[ 3 ]
		};
	},

	_proportionallyResize: function() {

		if (!this._proportionallyResizeElements.length) {
			return;
		}

		var prel,
			i = 0,
			element = this.helper || this.element;

		for ( ; i < this._proportionallyResizeElements.length; i++) {

			prel = this._proportionallyResizeElements[i];

			// TODO: Seems like a bug to cache this.outerDimensions
			// considering that we are in a loop.
			if (!this.outerDimensions) {
				this.outerDimensions = this._getPaddingPlusBorderDimensions( prel );
			}

			prel.css({
				height: (element.height() - this.outerDimensions.height) || 0,
				width: (element.width() - this.outerDimensions.width) || 0
			});

		}

	},

	_renderProxy: function() {

		var el = this.element, o = this.options;
		this.elementOffset = el.offset();

		if (this._helper) {

			this.helper = this.helper || $("<div style='overflow:hidden;'></div>");

			this.helper.addClass(this._helper).css({
				width: this.element.outerWidth() - 1,
				height: this.element.outerHeight() - 1,
				position: "absolute",
				left: this.elementOffset.left + "px",
				top: this.elementOffset.top + "px",
				zIndex: ++o.zIndex //TODO: Don't modify option
			});

			this.helper
				.appendTo("body")
				.disableSelection();

		} else {
			this.helper = this.element;
		}

	},

	_change: {
		e: function(event, dx) {
			return { width: this.originalSize.width + dx };
		},
		w: function(event, dx) {
			var cs = this.originalSize, sp = this.originalPosition;
			return { left: sp.left + dx, width: cs.width - dx };
		},
		n: function(event, dx, dy) {
			var cs = this.originalSize, sp = this.originalPosition;
			return { top: sp.top + dy, height: cs.height - dy };
		},
		s: function(event, dx, dy) {
			return { height: this.originalSize.height + dy };
		},
		se: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments),
				this._change.e.apply(this, [ event, dx, dy ]));
		},
		sw: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments),
				this._change.w.apply(this, [ event, dx, dy ]));
		},
		ne: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments),
				this._change.e.apply(this, [ event, dx, dy ]));
		},
		nw: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments),
				this._change.w.apply(this, [ event, dx, dy ]));
		}
	},

	_propagate: function(n, event) {
		$.ui.plugin.call(this, n, [ event, this.ui() ]);
		(n !== "resize" && this._trigger(n, event, this.ui()));
	},

	plugins: {},

	ui: function() {
		return {
			originalElement: this.originalElement,
			element: this.element,
			helper: this.helper,
			position: this.position,
			size: this.size,
			originalSize: this.originalSize,
			originalPosition: this.originalPosition
		};
	}

});

/*
 * Resizable Extensions
 */

$.ui.plugin.add("resizable", "animate", {

	stop: function( event ) {
		var that = $(this).resizable( "instance" ),
			o = that.options,
			pr = that._proportionallyResizeElements,
			ista = pr.length && (/textarea/i).test(pr[0].nodeName),
			soffseth = ista && that._hasScroll(pr[0], "left") ? 0 : that.sizeDiff.height,
			soffsetw = ista ? 0 : that.sizeDiff.width,
			style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
			left = (parseInt(that.element.css("left"), 10) +
				(that.position.left - that.originalPosition.left)) || null,
			top = (parseInt(that.element.css("top"), 10) +
				(that.position.top - that.originalPosition.top)) || null;

		that.element.animate(
			$.extend(style, top && left ? { top: top, left: left } : {}), {
				duration: o.animateDuration,
				easing: o.animateEasing,
				step: function() {

					var data = {
						width: parseInt(that.element.css("width"), 10),
						height: parseInt(that.element.css("height"), 10),
						top: parseInt(that.element.css("top"), 10),
						left: parseInt(that.element.css("left"), 10)
					};

					if (pr && pr.length) {
						$(pr[0]).css({ width: data.width, height: data.height });
					}

					// propagating resize, and updating values for each animation step
					that._updateCache(data);
					that._propagate("resize", event);

				}
			}
		);
	}

});

$.ui.plugin.add( "resizable", "containment", {

	start: function() {
		var element, p, co, ch, cw, width, height,
			that = $( this ).resizable( "instance" ),
			o = that.options,
			el = that.element,
			oc = o.containment,
			ce = ( oc instanceof $ ) ? oc.get( 0 ) : ( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;

		if ( !ce ) {
			return;
		}

		that.containerElement = $( ce );

		if ( /document/.test( oc ) || oc === document ) {
			that.containerOffset = {
				left: 0,
				top: 0
			};
			that.containerPosition = {
				left: 0,
				top: 0
			};

			that.parentData = {
				element: $( document ),
				left: 0,
				top: 0,
				width: $( document ).width(),
				height: $( document ).height() || document.body.parentNode.scrollHeight
			};
		} else {
			element = $( ce );
			p = [];
			$([ "Top", "Right", "Left", "Bottom" ]).each(function( i, name ) {
				p[ i ] = that._num( element.css( "padding" + name ) );
			});

			that.containerOffset = element.offset();
			that.containerPosition = element.position();
			that.containerSize = {
				height: ( element.innerHeight() - p[ 3 ] ),
				width: ( element.innerWidth() - p[ 1 ] )
			};

			co = that.containerOffset;
			ch = that.containerSize.height;
			cw = that.containerSize.width;
			width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw );
			height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;

			that.parentData = {
				element: ce,
				left: co.left,
				top: co.top,
				width: width,
				height: height
			};
		}
	},

	resize: function( event ) {
		var woset, hoset, isParent, isOffsetRelative,
			that = $( this ).resizable( "instance" ),
			o = that.options,
			co = that.containerOffset,
			cp = that.position,
			pRatio = that._aspectRatio || event.shiftKey,
			cop = {
				top: 0,
				left: 0
			},
			ce = that.containerElement,
			continueResize = true;

		if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) {
			cop = co;
		}

		if ( cp.left < ( that._helper ? co.left : 0 ) ) {
			that.size.width = that.size.width +
				( that._helper ?
					( that.position.left - co.left ) :
					( that.position.left - cop.left ) );

			if ( pRatio ) {
				that.size.height = that.size.width / that.aspectRatio;
				continueResize = false;
			}
			that.position.left = o.helper ? co.left : 0;
		}

		if ( cp.top < ( that._helper ? co.top : 0 ) ) {
			that.size.height = that.size.height +
				( that._helper ?
					( that.position.top - co.top ) :
					that.position.top );

			if ( pRatio ) {
				that.size.width = that.size.height * that.aspectRatio;
				continueResize = false;
			}
			that.position.top = that._helper ? co.top : 0;
		}

		isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );
		isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) );

		if ( isParent && isOffsetRelative ) {
			that.offset.left = that.parentData.left + that.position.left;
			that.offset.top = that.parentData.top + that.position.top;
		} else {
			that.offset.left = that.element.offset().left;
			that.offset.top = that.element.offset().top;
		}

		woset = Math.abs( that.sizeDiff.width +
			(that._helper ?
				that.offset.left - cop.left :
				(that.offset.left - co.left)) );

		hoset = Math.abs( that.sizeDiff.height +
			(that._helper ?
				that.offset.top - cop.top :
				(that.offset.top - co.top)) );

		if ( woset + that.size.width >= that.parentData.width ) {
			that.size.width = that.parentData.width - woset;
			if ( pRatio ) {
				that.size.height = that.size.width / that.aspectRatio;
				continueResize = false;
			}
		}

		if ( hoset + that.size.height >= that.parentData.height ) {
			that.size.height = that.parentData.height - hoset;
			if ( pRatio ) {
				that.size.width = that.size.height * that.aspectRatio;
				continueResize = false;
			}
		}

		if ( !continueResize ) {
			that.position.left = that.prevPosition.left;
			that.position.top = that.prevPosition.top;
			that.size.width = that.prevSize.width;
			that.size.height = that.prevSize.height;
		}
	},

	stop: function() {
		var that = $( this ).resizable( "instance" ),
			o = that.options,
			co = that.containerOffset,
			cop = that.containerPosition,
			ce = that.containerElement,
			helper = $( that.helper ),
			ho = helper.offset(),
			w = helper.outerWidth() - that.sizeDiff.width,
			h = helper.outerHeight() - that.sizeDiff.height;

		if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) {
			$( this ).css({
				left: ho.left - cop.left - co.left,
				width: w,
				height: h
			});
		}

		if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) {
			$( this ).css({
				left: ho.left - cop.left - co.left,
				width: w,
				height: h
			});
		}
	}
});

$.ui.plugin.add("resizable", "alsoResize", {

	start: function() {
		var that = $(this).resizable( "instance" ),
			o = that.options;

		$(o.alsoResize).each(function() {
			var el = $(this);
			el.data("ui-resizable-alsoresize", {
				width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
				left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
			});
		});
	},

	resize: function(event, ui) {
		var that = $(this).resizable( "instance" ),
			o = that.options,
			os = that.originalSize,
			op = that.originalPosition,
			delta = {
				height: (that.size.height - os.height) || 0,
				width: (that.size.width - os.width) || 0,
				top: (that.position.top - op.top) || 0,
				left: (that.position.left - op.left) || 0
			};

			$(o.alsoResize).each(function() {
				var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
					css = el.parents(ui.originalElement[0]).length ?
							[ "width", "height" ] :
							[ "width", "height", "top", "left" ];

				$.each(css, function(i, prop) {
					var sum = (start[prop] || 0) + (delta[prop] || 0);
					if (sum && sum >= 0) {
						style[prop] = sum || null;
					}
				});

				el.css(style);
			});
	},

	stop: function() {
		$(this).removeData("resizable-alsoresize");
	}
});

$.ui.plugin.add("resizable", "ghost", {

	start: function() {

		var that = $(this).resizable( "instance" ), o = that.options, cs = that.size;

		that.ghost = that.originalElement.clone();
		that.ghost
			.css({
				opacity: 0.25,
				display: "block",
				position: "relative",
				height: cs.height,
				width: cs.width,
				margin: 0,
				left: 0,
				top: 0
			})
			.addClass("ui-resizable-ghost")
			.addClass(typeof o.ghost === "string" ? o.ghost : "");

		that.ghost.appendTo(that.helper);

	},

	resize: function() {
		var that = $(this).resizable( "instance" );
		if (that.ghost) {
			that.ghost.css({
				position: "relative",
				height: that.size.height,
				width: that.size.width
			});
		}
	},

	stop: function() {
		var that = $(this).resizable( "instance" );
		if (that.ghost && that.helper) {
			that.helper.get(0).removeChild(that.ghost.get(0));
		}
	}

});

$.ui.plugin.add("resizable", "grid", {

	resize: function() {
		var outerDimensions,
			that = $(this).resizable( "instance" ),
			o = that.options,
			cs = that.size,
			os = that.originalSize,
			op = that.originalPosition,
			a = that.axis,
			grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid,
			gridX = (grid[0] || 1),
			gridY = (grid[1] || 1),
			ox = Math.round((cs.width - os.width) / gridX) * gridX,
			oy = Math.round((cs.height - os.height) / gridY) * gridY,
			newWidth = os.width + ox,
			newHeight = os.height + oy,
			isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
			isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
			isMinWidth = o.minWidth && (o.minWidth > newWidth),
			isMinHeight = o.minHeight && (o.minHeight > newHeight);

		o.grid = grid;

		if (isMinWidth) {
			newWidth += gridX;
		}
		if (isMinHeight) {
			newHeight += gridY;
		}
		if (isMaxWidth) {
			newWidth -= gridX;
		}
		if (isMaxHeight) {
			newHeight -= gridY;
		}

		if (/^(se|s|e)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
		} else if (/^(ne)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.top = op.top - oy;
		} else if (/^(sw)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.left = op.left - ox;
		} else {
			if ( newHeight - gridY <= 0 || newWidth - gridX <= 0) {
				outerDimensions = that._getPaddingPlusBorderDimensions( this );
			}

			if ( newHeight - gridY > 0 ) {
				that.size.height = newHeight;
				that.position.top = op.top - oy;
			} else {
				newHeight = gridY - outerDimensions.height;
				that.size.height = newHeight;
				that.position.top = op.top + os.height - newHeight;
			}
			if ( newWidth - gridX > 0 ) {
				that.size.width = newWidth;
				that.position.left = op.left - ox;
			} else {
				newWidth = gridX - outerDimensions.width;
				that.size.width = newWidth;
				that.position.left = op.left + os.width - newWidth;
			}
		}
	}

});

var resizable = $.ui.resizable;



}));
/*! Copyright (c) 2011 by Jonas Mosbech - https://github.com/jmosbech/StickyTableHeaders
    MIT license info: https://github.com/jmosbech/StickyTableHeaders/blob/master/license.txt */

/*
 * Source: http://jsfiddle.net/LqZ2T/15/
 * Updated by: Greg Mercer
 * For: www.JungleScout.com
 */

//If the file has injected many times
if ($('.jsContainer').length >= 1) {
  throw new Error('Injected!')
}

;(function($, window, undefined) {
  'use strict'

  var pluginName = 'stickyTableHeaders'
  var id = 0
  var defaults = {
    fixedOffset: 0,
    container: null
  }

  /*
   * This was taken from stackoverflow:
   * http://stackoverflow.com/questions/7501761/div-scrollbar-width
   */
  function getScrollbarWidth() {
    var div = $(
      '<div style="width:50px;height:50px;overflow:hidden;position:absolute;top:-200px;left:-200px;"><div style="height:100px;"></div></div>'
    )
    $('body').append(div)
    var w1 = $('div', div).innerWidth()
    div.css('overflow-y', 'auto')
    var w2 = $('div', div).innerWidth()
    $(div).remove()
    return w1 - w2
  }

  function Plugin(el, options) {
    // To avoid scope issues, use 'base' instead of 'this'
    // to reference this class from internal events and functions.
    var base = this
    // Access to jQuery and DOM versions of element
    base.$el = $(el)
    base.el = el
    base.id = id++

    // Listen for destroyed, call teardown
    base.$el.bind('destroyed', $.proxy(base.teardown, base))

    /* Need to use element to get offset if container is window.
     * Otherwise use container's offset for calculations.
     */
    base.getContainerOffset = function() {
      var c_offset = base.$container[0].getBoundingClientRect()
      var e_offset = base.$el[0].getBoundingClientRect()
      return c_offset === null ? { top: 0, left: e_offset.left } : c_offset
    }

    base.init = function() {
      base.setOptions(options)
      /* We need to know how much to scroll to activate and deactivate the
       * sticky header. I originally tried to calculate this using .position
       * on the child element. This works fine so long as the parent element
       * has its position set to something other than "static". Please
       * see this stackoverflow thread for why:
       *  http://stackoverflow.com/questions/2842432/jquery-position-isnt-returning-offset-relative-to-parent
       *
       * So to get this to work everywhere, I grab the difference from
       * the top of the child element and the top of the parent element
       * when the page loads. This should tell us how much we need to
       * scroll to activate the sticky header.
       *
       * Also - the offset function does not seem to take any table
       * captions into consideration. So we check for a table caption
       * and add this in to the amount we need to scroll for an
       * activation.
       *
       * This part has been changed by Mohammad M. AlBanna
       * Website: www.MBanna.info
       * For: www.JungleScout.com
       */

      var startTopOffset =
        base.getContainerOffset().top - Math.abs(base.$el[0].getBoundingClientRect().top)
      var caption = base.$el.find('caption')
      if (caption.length) {
        startTopOffset += caption.height()
      }
      base.scrollAmountToActivate = startTopOffset
      base.scrollAmountToDeactivate = base.scrollAmountToActivate + base.$el.height()

      /* See notes in updateWidth for why we need this*/
      base.parentClientWidth = base.$container.width() - getScrollbarWidth()

      // Keep track of state
      base.isCloneVisible = false
      base.leftOffset = null
      base.topOffset = null

      base.$el.each(function() {
        var $this = $(this)

        $this.wrap('<div class="divTableWithFloatingHeader"></div>')
        $this.find('thead.tableFloatingHeader').remove()

        base.$originalHeader = $('thead', this).filter(':first')
        base.$clonedHeader = base.$originalHeader.clone()

        base.$clonedHeader.addClass('tableFloatingHeader')
        base.$clonedHeader.css({
          position: 'fixed',
          top: 0,
          'z-index': 1, // #18: opacity bug
          display: 'none'
        })

        base.$originalHeader.addClass('tableFloatingHeaderOriginal')
        base.$originalHeader.after(base.$clonedHeader)

        // enabling support for jquery.tablesorter plugin
        // forward clicks on clone to original
        $('th', base.$clonedHeader).click(function(e) {
          var index = $('th', base.$clonedHeader).index(this)
          $('th', base.$originalHeader)
            .eq(index)
            .click()
        })
        $this.bind('sortEnd', base.updateWidth)
      })

      base.updateWidth()
      base.toggleHeaders()
      base.bind()
    }

    base.setOptions = function(options) {
      base.options = $.extend({}, defaults, options)
      base.$window = $(window)
      base.$clonedHeader = null
      base.$originalHeader = null
      base.$container = base.options.container != null ? $(base.options.container) : base.$window
    }

    base.toggleHeaders = function() {
      base.$el.each(function() {
        var $this = $(this)

        var newTopOffset = isNaN(base.options.fixedOffset)
          ? base.options.fixedOffset.height()
          : base.options.fixedOffset
        var offset = base.getContainerOffset()
        var scrollTop = base.$container.scrollTop() + newTopOffset
        var scrollLeft = base.$container.scrollLeft()
        if (scrollTop > base.scrollAmountToActivate && scrollTop < base.scrollAmountToDeactivate) {
          var newLeft = offset.left - scrollLeft
          if (
            base.isCloneVisible &&
            newLeft === base.leftOffset &&
            newTopOffset === base.topOffset
          ) {
            return
          }

          base.$clonedHeader.css({
            top: newTopOffset + offset.top,
            'margin-top': 0,
            left: newLeft,
            display: 'block'
          })
          base.updateWidth()

          base.$originalHeader.css('visibility', 'hidden')
          base.isCloneVisible = true
          base.leftOffset = newLeft
          base.topOffset = newTopOffset
        } else if (base.isCloneVisible) {
          base.$clonedHeader.css('display', 'none')
          base.$originalHeader.css('visibility', 'visible')
          base.isCloneVisible = false
        }
      })
    }

    base.updateWidth = function() {
      // Copy cell widths and classes from original header
      $('th', base.$clonedHeader).each(function(index) {
        var $this = $(this)
        var $origCell = $('th', base.$originalHeader).eq(index)
        this.className = $origCell.attr('class') || ''
        $this.css('min-width', $origCell.outerWidth())
      })

      // Copy row width from whole table
      base.$clonedHeader.css('width', base.$originalHeader.width())

      // One last thing - if our table is inside of another
      // scrolled div, the width of our parent div could
      // be less than that of the cloned header.
      // This would cause the cloned div to display outside
      // of our parent's viewport and would appear "on top of"
      // any scrollbars on our parent. Need to clip.
      if (
        base.$clonedHeader.width() > base.parentClientWidth ||
        base.$clonedHeader.width() < base.parentClientWidth
      ) {
        var scrollLeft = base.$container.scrollLeft()
        var clipLeft = scrollLeft
        var clipRight = base.$container.width() + scrollLeft
        base.$clonedHeader.css({
          clip:
            'rect(0px, ' +
            clipRight +
            'px, ' +
            base.$clonedHeader.height() +
            'px,' +
            clipLeft +
            'px)'
        })
      }
    }

    base.destroy = function() {
      base.$el.unbind('destroyed', base.teardown)
      base.teardown()
    }

    base.teardown = function() {
      $.removeData(base.el, 'plugin_' + pluginName)
      if (typeof base != 'undefined') {
        base.unbind()
        base.$clonedHeader.remove()
        base.$originalHeader.removeClass('tableFloatingHeaderOriginal')
        base.$originalHeader.css('visibility', 'visible')
        base.$el.remove('thead.tableFloatingHeader')
        base.el = null
        base.$el = null
      }
    }

    base.bind = function() {
      base.$container.on('scroll.' + pluginName + base.id, base.toggleHeaders)
      base.$container.on('resize.' + pluginName + base.id, base.toggleHeaders)
      base.$container.on('resize.' + pluginName + base.id, base.updateWidth)
    }

    base.unbind = function() {
      base.$container.off('scroll.' + pluginName + base.id, base.toggleHeaders)
      base.$container.off('resize.' + pluginName + base.id, base.toggleHeaders)
      base.$container.off('resize.' + pluginName + base.id, base.updateWidth)
    }

    base.updateOptions = function(options) {
      base.unbind()
      base.bind()
      base.updateWidth()
      base.toggleHeaders()
    }

    // Run initializer
    base.init()
  }

  // A really lightweight plugin wrapper around the constructor,
  // preventing against multiple instantiations
  $.fn[pluginName] = function(options) {
    return this.each(function() {
      var instance = $.data(this, 'plugin_' + pluginName)
      if (instance) {
        if (typeof options === 'string') {
          instance[options].apply(instance)
        } else {
          instance.updateOptions(options)
        }
      } else if (typeof instance == 'undefined' && options !== 'destroy') {
        $.data(this, 'plugin_' + pluginName, new Plugin(this, options))
      }
    })
  }
})(jQuery, window)

/*! TableSorter (FORK) v2.22.1 */ /*
 * Client-side table sorting with ease!
 * @requires jQuery v1.2.6+
 *
 * Copyright (c) 2007 Christian Bach
 * fork maintained by Rob Garrison
 *
 * Examples and docs at: http://tablesorter.com
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 * @type jQuery
 * @name tablesorter (FORK)
 * @cat Plugins/Tablesorter
 * @author Christian Bach - christian.bach@polyester.se
 * @contributor Rob Garrison - https://github.com/Mottie/tablesorter
 */
/*jshint browser:true, jquery:true, unused:false, expr: true */
/*global console:false, alert:false, require:false, define:false, module:false */

//If the file has injected many times
if ($('.jsContainer').length >= 1) {
  throw new Error('Injected!')
}

;(function($) {
  'use strict'

  $.extend({
    /*jshint supernew:true */
    tablesorter: new function() {
      var ts = this

      ts.version = '2.22.1'

      ts.parsers = []
      ts.widgets = []
      ts.defaults = {
        // *** appearance
        theme: 'default', // adds tablesorter-{theme} to the table for styling
        widthFixed: false, // adds colgroup to fix widths of columns
        showProcessing: false, // show an indeterminate timer icon in the header when the table is sorted or filtered.

        headerTemplate: '{content}', // header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> (class from cssIcon)
        onRenderTemplate: null, // function(index, template){ return template; }, (template is a string)
        onRenderHeader: null, // function(index){}, (nothing to return)

        // *** functionality
        cancelSelection: true, // prevent text selection in the header
        tabIndex: true, // add tabindex to header for keyboard accessibility
        dateFormat: 'mmddyyyy', // other options: 'ddmmyyy' or 'yyyymmdd'
        sortMultiSortKey: 'shiftKey', // key used to select additional columns
        sortResetKey: 'ctrlKey', // key used to remove sorting on a column
        usNumberFormat: true, // false for German '1.234.567,89' or French '1 234 567,89'
        delayInit: false, // if false, the parsed table contents will not update until the first sort
        serverSideSorting: false, // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
        resort: true, // default setting to trigger a resort after an 'update', 'addRows', 'updateCell', etc has completed

        // *** sort options
        headers: {}, // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
        ignoreCase: true, // ignore case while sorting
        sortForce: null, // column(s) first sorted; always applied
        sortList: [], // Initial sort order; applied initially; updated when manually sorted
        sortAppend: null, // column(s) sorted last; always applied
        sortStable: false, // when sorting two rows with exactly the same content, the original sort order is maintained

        sortInitialOrder: 'asc', // sort direction on first click
        sortLocaleCompare: false, // replace equivalent character (accented characters)
        sortReset: false, // third click on the header will reset column to default - unsorted
        sortRestart: false, // restart sort to 'sortInitialOrder' when clicking on previously unsorted columns

        emptyTo: 'bottom', // sort empty cell to bottom, top, none, zero, emptyMax, emptyMin
        stringTo: 'max', // sort strings in numerical column as max, min, top, bottom, zero
        textExtraction: 'basic', // text extraction method/function - function(node, table, cellIndex){}
        textAttribute: 'data-text', // data-attribute that contains alternate cell text (used in default textExtraction function)
        textSorter: null, // choose overall or specific column sorter function(a, b, direction, table, columnIndex) [alt: ts.sortText]
        numberSorter: null, // choose overall numeric sorter function(a, b, direction, maxColumnValue)

        // *** widget options
        widgets: [], // method to add widgets, e.g. widgets: ['zebra']
        widgetOptions: {
          zebra: ['even', 'odd'] // zebra widget alternating row class names
        },
        initWidgets: true, // apply widgets on tablesorter initialization
        widgetClass: 'widget-{name}', // table class name template to match to include a widget

        // *** callbacks
        initialized: null, // function(table){},

        // *** extra css class names
        tableClass: '',
        cssAsc: '',
        cssDesc: '',
        cssNone: '',
        cssHeader: '',
        cssHeaderRow: '',
        cssProcessing: '', // processing icon applied to header during sort/filter

        cssChildRow: 'tablesorter-childRow', // class name indiciating that a row is to be attached to the its parent
        cssIcon: 'tablesorter-icon', // if this class does not exist, the {icon} will not be added from the headerTemplate
        cssIconNone: '', // class name added to the icon when there is no column sort
        cssIconAsc: '', // class name added to the icon when the column has an ascending sort
        cssIconDesc: '', // class name added to the icon when the column has a descending sort
        cssInfoBlock: 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
        cssNoSort: 'tablesorter-noSort', // class name added to element inside header; clicking on it won't cause a sort
        cssIgnoreRow: 'tablesorter-ignoreRow', // header row to ignore; cells within this row will not be added to c.$headers

        // *** events
        pointerClick: 'click',
        pointerDown: 'mousedown',
        pointerUp: 'mouseup',

        // *** selectors
        selectorHeaders: '> thead th, > thead td',
        selectorSort: 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
        selectorRemove: '.remove-me',

        // *** advanced
        debug: false,

        // *** Internal variables
        headerList: [],
        empties: {},
        strings: {},
        parsers: []

        // removed: widgetZebra: { css: ['even', 'odd'] }
      }

      // internal css classes - these will ALWAYS be added to
      // the table and MUST only contain one class name - fixes #381
      ts.css = {
        table: 'tablesorter',
        cssHasChild: 'tablesorter-hasChildRow',
        childRow: 'tablesorter-childRow',
        colgroup: 'tablesorter-colgroup',
        header: 'tablesorter-header',
        headerRow: 'tablesorter-headerRow',
        headerIn: 'tablesorter-header-inner',
        icon: 'tablesorter-icon',
        processing: 'tablesorter-processing',
        sortAsc: 'tablesorter-headerAsc',
        sortDesc: 'tablesorter-headerDesc',
        sortNone: 'tablesorter-headerUnSorted'
      }

      // labels applied to sortable headers for accessibility (aria) support
      ts.language = {
        sortAsc: 'Ascending sort applied, ',
        sortDesc: 'Descending sort applied, ',
        sortNone: 'No sort applied, ',
        nextAsc: 'activate to apply an ascending sort',
        nextDesc: 'activate to apply a descending sort',
        nextNone: 'activate to remove the sort'
      }

      // These methods can be applied on table.config instance
      ts.instanceMethods = {}

      /* debuging utils */
      function log() {
        var a = arguments[0],
          s = arguments.length > 1 ? Array.prototype.slice.call(arguments) : a
        if (typeof console !== 'undefined' && typeof console.log !== 'undefined') {
          console[/error/i.test(a) ? 'error' : /warn/i.test(a) ? 'warn' : 'log'](s)
        } else {
          alert(s)
        }
      }

      function benchmark(s, d) {
        log(s + ' (' + (new Date().getTime() - d.getTime()) + 'ms)')
      }

      ts.log = log
      ts.benchmark = benchmark

      // $.isEmptyObject from jQuery v1.4
      function isEmptyObject(obj) {
        /*jshint forin: false */
        for (var name in obj) {
          return false
        }
        return true
      }

      ts.getElementText = function(c, node, cellIndex) {
        if (!node) {
          return ''
        }
        var te,
          t = c.textExtraction || '',
          // node could be a jquery object
          // http://jsperf.com/jquery-vs-instanceof-jquery/2
          $node = node.jquery ? node : $(node)
        if (typeof t === 'string') {
          // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow!
          // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/
          return $.trim(
            (t === 'basic' ? $node.attr(c.textAttribute) || node.textContent : node.textContent) ||
              $node.text()
          )
        } else {
          if (typeof t === 'function') {
            return $.trim(t($node[0], c.table, cellIndex))
          } else if (typeof (te = ts.getColumnData(c.table, t, cellIndex)) === 'function') {
            return $.trim(te($node[0], c.table, cellIndex))
          }
        }
        // fallback
        return $.trim($node[0].textContent || $node.text())
      }

      function detectParserForColumn(table, rows, rowIndex, cellIndex) {
        var cur,
          $node,
          c = table.config,
          i = ts.parsers.length,
          node = false,
          nodeValue = '',
          keepLooking = true
        while (nodeValue === '' && keepLooking) {
          rowIndex++
          if (rows[rowIndex]) {
            node = rows[rowIndex].cells[cellIndex]
            nodeValue = ts.getElementText(c, node, cellIndex)
            $node = $(node)
            if (table.config.debug) {
              log(
                'Checking if value was empty on row ' +
                  rowIndex +
                  ', column: ' +
                  cellIndex +
                  ': "' +
                  nodeValue +
                  '"'
              )
            }
          } else {
            keepLooking = false
          }
        }
        while (--i >= 0) {
          cur = ts.parsers[i]
          // ignore the default text parser because it will always be true
          if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, table, node, $node)) {
            return cur
          }
        }
        // nothing found, return the generic parser (text)
        return ts.getParserById('text')
      }

      // centralized function to extract/parse cell contents
      function getParsedText(c, cell, colIndex, txt) {
        if (typeof txt === 'undefined') {
          txt = ts.getElementText(c, cell, colIndex)
        }
        // if no parser, make sure to return the txt
        var val = '' + txt,
          parser = c.parsers[colIndex],
          extractor = c.extractors[colIndex]
        if (parser) {
          // do extract before parsing, if there is one
          if (extractor && typeof extractor.format === 'function') {
            txt = extractor.format(txt, c.table, cell, colIndex)
          }
          // allow parsing if the string is empty, previously parsing would change it to zero,
          // in case the parser needs to extract data from the table cell attributes
          val =
            parser.id === 'no-parser'
              ? ''
              : // make sure txt is a string (extractor may have converted it)
                parser.format('' + txt, c.table, cell, colIndex)
          if (c.ignoreCase && typeof val === 'string') {
            val = val.toLowerCase()
          }
        }
        return val
      }

      function buildParserCache(table) {
        var c = table.config,
          // update table bodies in case we start with an empty table
          tb = (c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')')),
          rows,
          list,
          l,
          i,
          h,
          ch,
          np,
          p,
          e,
          time,
          j = 0,
          parsersDebug = '',
          len = tb.length
        if (len === 0) {
          return c.debug ? log('Warning: *Empty table!* Not building a parser cache') : ''
        } else if (c.debug) {
          time = new Date()
          log('Detecting parsers for each column')
        }
        list = {
          extractors: [],
          parsers: []
        }
        while (j < len) {
          rows = tb[j].rows
          if (rows.length) {
            l = c.columns // rows[j].cells.length;
            for (i = 0; i < l; i++) {
              h = c.$headerIndexed[i]
              // get column indexed table cell
              ch = ts.getColumnData(table, c.headers, i)
              // get column parser/extractor
              e = ts.getParserById(ts.getData(h, ch, 'extractor'))
              p = ts.getParserById(ts.getData(h, ch, 'sorter'))
              np = ts.getData(h, ch, 'parser') === 'false'
              // empty cells behaviour - keeping emptyToBottom for backwards compatibility
              c.empties[i] = (
                ts.getData(h, ch, 'empty') ||
                c.emptyTo ||
                (c.emptyToBottom ? 'bottom' : 'top')
              ).toLowerCase()
              // text strings behaviour in numerical sorts
              c.strings[i] = (ts.getData(h, ch, 'string') || c.stringTo || 'max').toLowerCase()
              if (np) {
                p = ts.getParserById('no-parser')
              }
              if (!e) {
                // For now, maybe detect someday
                e = false
              }
              if (!p) {
                p = detectParserForColumn(table, rows, -1, i)
              }
              if (c.debug) {
                parsersDebug +=
                  'column:' +
                  i +
                  '; extractor:' +
                  e.id +
                  '; parser:' +
                  p.id +
                  '; string:' +
                  c.strings[i] +
                  '; empty: ' +
                  c.empties[i] +
                  '\n'
              }
              list.parsers[i] = p
              list.extractors[i] = e
            }
          }
          j += list.parsers.length ? len : 1
        }
        if (c.debug) {
          log(parsersDebug ? parsersDebug : 'No parsers detected')
          benchmark('Completed detecting parsers', time)
        }
        c.parsers = list.parsers
        c.extractors = list.extractors
      }

      /* utils */
      function buildCache(table) {
        var cc,
          t,
          v,
          i,
          j,
          k,
          $row,
          cols,
          cacheTime,
          totalRows,
          rowData,
          prevRowData,
          colMax,
          c = table.config,
          $tb = c.$tbodies,
          parsers = c.parsers
        c.cache = {}
        c.totalRows = 0
        // if no parsers found, return - it's an empty table.
        if (!parsers) {
          return c.debug ? log('Warning: *Empty table!* Not building a cache') : ''
        }
        if (c.debug) {
          cacheTime = new Date()
        }
        // processing icon
        if (c.showProcessing) {
          ts.isProcessing(table, true)
        }
        for (k = 0; k < $tb.length; k++) {
          colMax = [] // column max value per tbody
          cc = c.cache[k] = {
            normalized: [] // array of normalized row data; last entry contains 'rowData' above
            // colMax: #   // added at the end
          }

          totalRows = ($tb[k] && $tb[k].rows.length) || 0
          for (i = 0; i < totalRows; ++i) {
            rowData = {
              // order: original row order #
              // $row : jQuery Object[]
              child: [], // child row text (filter widget)
              raw: [] // original row text
            }
            /** Add the table data to main data array */
            $row = $($tb[k].rows[i])
            cols = []
            // if this is a child row, add it to the last row's children and continue to the next row
            // ignore child row class, if it is the first row
            if ($row.hasClass(c.cssChildRow) && i !== 0) {
              t = cc.normalized.length - 1
              prevRowData = cc.normalized[t][c.columns]
              prevRowData.$row = prevRowData.$row.add($row)
              // add 'hasChild' class name to parent row
              if (!$row.prev().hasClass(c.cssChildRow)) {
                $row.prev().addClass(ts.css.cssHasChild)
              }
              // save child row content (un-parsed!)
              v = $row.children('th, td')
              t = prevRowData.child.length
              prevRowData.child[t] = []
              // child row content does not account for colspans/rowspans; so indexing may be off
              for (j = 0; j < c.columns; j++) {
                prevRowData.child[t][j] = getParsedText(c, v[j], j)
              }
              // go to the next for loop
              continue
            }
            rowData.$row = $row
            rowData.order = i // add original row position to rowCache
            for (j = 0; j < c.columns; ++j) {
              if (typeof parsers[j] === 'undefined') {
                if (c.debug) {
                  log('No parser found for cell:', $row[0].cells[j], 'does it have a header?')
                }
                continue
              }
              t = ts.getElementText(c, $row[0].cells[j], j)
              rowData.raw.push(t) // save original row text
              v = getParsedText(c, $row[0].cells[j], j, t)
              cols.push(v)
              if ((parsers[j].type || '').toLowerCase() === 'numeric') {
                // determine column max value (ignore sign)
                colMax[j] = Math.max(Math.abs(v) || 0, colMax[j] || 0)
              }
            }
            // ensure rowData is always in the same location (after the last column)
            cols[c.columns] = rowData
            cc.normalized.push(cols)
          }
          cc.colMax = colMax
          // total up rows, not including child rows
          c.totalRows += cc.normalized.length
        }
        if (c.showProcessing) {
          ts.isProcessing(table) // remove processing icon
        }
        if (c.debug) {
          benchmark('Building cache for ' + totalRows + ' rows', cacheTime)
        }
      }

      // init flag (true) used by pager plugin to prevent widget application
      function appendToTable(table, init) {
        var c = table.config,
          wo = c.widgetOptions,
          $tbodies = c.$tbodies,
          rows = [],
          cc = c.cache,
          n,
          totalRows,
          $bk,
          $tb,
          i,
          k,
          appendTime
        // empty table - fixes #206/#346
        if (isEmptyObject(cc)) {
          // run pager appender in case the table was just emptied
          return c.appender
            ? c.appender(table, rows)
            : table.isUpdating
            ? c.$table.trigger('updateComplete', table)
            : '' // Fixes #532
        }
        if (c.debug) {
          appendTime = new Date()
        }
        for (k = 0; k < $tbodies.length; k++) {
          $bk = $tbodies.eq(k)
          if ($bk.length) {
            // get tbody
            $tb = ts.processTbody(table, $bk, true)
            n = cc[k].normalized
            totalRows = n.length
            for (i = 0; i < totalRows; i++) {
              rows.push(n[i][c.columns].$row)
              // removeRows used by the pager plugin; don't render if using ajax - fixes #411
              if (
                !c.appender ||
                (c.pager && (!c.pager.removeRows || !wo.pager_removeRows) && !c.pager.ajax)
              ) {
                $tb.append(n[i][c.columns].$row)
              }
            }
            // restore tbody
            ts.processTbody(table, $tb, false)
          }
        }
        if (c.appender) {
          c.appender(table, rows)
        }
        if (c.debug) {
          benchmark('Rebuilt table', appendTime)
        }
        // apply table widgets; but not before ajax completes
        if (!init && !c.appender) {
          ts.applyWidget(table)
        }
        if (table.isUpdating) {
          c.$table.trigger('updateComplete', table)
        }
      }

      function formatSortingOrder(v) {
        // look for 'd' in 'desc' order; return true
        return /^d/i.test(v) || v === 1
      }

      function buildHeaders(table) {
        var ch,
          $t,
          h,
          i,
          t,
          lock,
          time,
          indx,
          c = table.config
        c.headerList = []
        c.headerContent = []
        if (c.debug) {
          time = new Date()
        }
        // children tr in tfoot - see issue #196 & #547
        c.columns = ts.computeColumnIndex(c.$table.children('thead, tfoot').children('tr'))
        // add icon if cssIcon option exists
        i = c.cssIcon
          ? '<i class="' +
            (c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon) +
            '"></i>'
          : ''
        // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
        c.$headers = $(
          $.map($(table).find(c.selectorHeaders), function(elem, index) {
            $t = $(elem)
            // ignore cell (don't add it to c.$headers) if row has ignoreRow class
            if ($t.parent().hasClass(c.cssIgnoreRow)) {
              return
            }
            // make sure to get header cell & not column indexed cell
            ch = ts.getColumnData(table, c.headers, index, true)
            // save original header content
            c.headerContent[index] = $t.html()
            // if headerTemplate is empty, don't reformat the header cell
            if (c.headerTemplate !== '' && !$t.find('.' + ts.css.headerIn).length) {
              // set up header template
              t = c.headerTemplate
                .replace(/\{content\}/g, $t.html())
                .replace(/\{icon\}/g, $t.find('.' + ts.css.icon).length ? '' : i)
              if (c.onRenderTemplate) {
                h = c.onRenderTemplate.apply($t, [index, t])
                if (h && typeof h === 'string') {
                  t = h
                } // only change t if something is returned
              }
              $t.html('<div class="' + ts.css.headerIn + '">' + t + '</div>') // faster than wrapInner
            }
            if (c.onRenderHeader) {
              c.onRenderHeader.apply($t, [index, c, c.$table])
            }
            // *** remove this.column value if no conflicts found
            elem.column = parseInt($t.attr('data-column'), 10)
            elem.order = formatSortingOrder(
              ts.getData($t, ch, 'sortInitialOrder') || c.sortInitialOrder
            )
              ? [1, 0, 2]
              : [0, 1, 2]
            elem.count = -1 // set to -1 because clicking on the header automatically adds one
            elem.lockedOrder = false
            lock = ts.getData($t, ch, 'lockedOrder') || false
            if (typeof lock !== 'undefined' && lock !== false) {
              elem.order = elem.lockedOrder = formatSortingOrder(lock) ? [1, 1, 1] : [0, 0, 0]
            }
            $t.addClass(ts.css.header + ' ' + c.cssHeader)
            // add cell to headerList
            c.headerList[index] = elem
            // add to parent in case there are multiple rows
            $t.parent()
              .addClass(ts.css.headerRow + ' ' + c.cssHeaderRow)
              .attr('role', 'row')
            // allow keyboard cursor to focus on element
            if (c.tabIndex) {
              $t.attr('tabindex', 0)
            }
            return elem
          })
        )
        // cache headers per column
        c.$headerIndexed = []
        for (indx = 0; indx < c.columns; indx++) {
          $t = c.$headers.filter('[data-column="' + indx + '"]')
          // target sortable column cells, unless there are none, then use non-sortable cells
          // .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6
          c.$headerIndexed[indx] = $t.not('.sorter-false').length
            ? $t.not('.sorter-false').filter(':last')
            : $t.filter(':last')
        }
        $(table)
          .find(c.selectorHeaders)
          .attr({
            scope: 'col',
            role: 'columnheader'
          })
        // enable/disable sorting
        updateHeader(table)
        if (c.debug) {
          benchmark('Built headers:', time)
          log(c.$headers)
        }
      }

      function commonUpdate(table, resort, callback) {
        var c = table.config
        // remove rows/elements before update
        c.$table.find(c.selectorRemove).remove()
        // rebuild parsers
        buildParserCache(table)
        // rebuild the cache map
        buildCache(table)
        checkResort(c, resort, callback)
      }

      function updateHeader(table) {
        var index,
          s,
          $th,
          col,
          c = table.config,
          len = c.$headers.length
        for (index = 0; index < len; index++) {
          $th = c.$headers.eq(index)
          col = ts.getColumnData(table, c.headers, index, true)
          // add 'sorter-false' class if 'parser-false' is set
          s =
            ts.getData($th, col, 'sorter') === 'false' || ts.getData($th, col, 'parser') === 'false'
          $th[0].sortDisabled = s
          $th[s ? 'addClass' : 'removeClass']('sorter-false').attr('aria-disabled', '' + s)
          // aria-controls - requires table ID
          if (table.id) {
            if (s) {
              $th.removeAttr('aria-controls')
            } else {
              $th.attr('aria-controls', table.id)
            }
          }
        }
      }

      function setHeadersCss(table) {
        var f,
          h,
          i,
          j,
          $headers,
          $h,
          nextSort,
          txt,
          c = table.config,
          list = c.sortList,
          len = list.length,
          none = ts.css.sortNone + ' ' + c.cssNone,
          css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
          cssIcon = [c.cssIconAsc, c.cssIconDesc, c.cssIconNone],
          aria = ['ascending', 'descending'],
          // find the footer
          $t = $(table)
            .find('tfoot tr')
            .children()
            .add($(c.namespace + '_extra_headers'))
            .removeClass(css.join(' '))
        // remove all header information
        c.$headers
          .removeClass(css.join(' '))
          .addClass(none)
          .attr('aria-sort', 'none')
          .find('.' + ts.css.icon)
          .removeClass(cssIcon.join(' '))
          .addClass(cssIcon[2])
        for (i = 0; i < len; i++) {
          // direction = 2 means reset!
          if (list[i][1] !== 2) {
            // multicolumn sorting updating - choose the :last in case there are nested columns
            f = c.$headers
              .not('.sorter-false')
              .filter('[data-column="' + list[i][0] + '"]' + (len === 1 ? ':last' : ''))
            if (f.length) {
              for (j = 0; j < f.length; j++) {
                if (!f[j].sortDisabled) {
                  f.eq(j)
                    .removeClass(none)
                    .addClass(css[list[i][1]])
                    .attr('aria-sort', aria[list[i][1]])
                    .find('.' + ts.css.icon)
                    .removeClass(cssIcon[2])
                    .addClass(cssIcon[list[i][1]])
                }
              }
              // add sorted class to footer & extra headers, if they exist
              if ($t.length) {
                $t.filter('[data-column="' + list[i][0] + '"]')
                  .removeClass(none)
                  .addClass(css[list[i][1]])
              }
            }
          }
        }
        // add verbose aria labels
        len = c.$headers.length
        $headers = c.$headers.not('.sorter-false')
        for (i = 0; i < len; i++) {
          $h = $headers.eq(i)
          if ($h.length) {
            h = $headers[i]
            ;(nextSort = h.order[(h.count + 1) % (c.sortReset ? 3 : 2)]),
              (txt =
                $.trim($h.text()) +
                ': ' +
                ts.language[
                  $h.hasClass(ts.css.sortAsc)
                    ? 'sortAsc'
                    : $h.hasClass(ts.css.sortDesc)
                    ? 'sortDesc'
                    : 'sortNone'
                ] +
                ts.language[nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone'])
            $h.attr('aria-label', txt)
          }
        }
      }

      function updateHeaderSortCount(table, list) {
        var col,
          dir,
          group,
          header,
          indx,
          primary,
          temp,
          val,
          c = table.config,
          sortList = list || c.sortList,
          len = sortList.length
        c.sortList = []
        for (indx = 0; indx < len; indx++) {
          val = sortList[indx]
          // ensure all sortList values are numeric - fixes #127
          col = parseInt(val[0], 10)
          // prevents error if sorton array is wrong
          if (col < c.columns && c.$headerIndexed[col]) {
            // make sure header exists
            header = c.$headerIndexed[col][0]
            // o.count = o.count + 1;
            dir = ('' + val[1]).match(/^(1|d|s|o|n)/)
            dir = dir ? dir[0] : ''
            // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
            switch (dir) {
              case '1':
              case 'd': // descending
                dir = 1
                break
              case 's': // same direction (as primary column)
                // if primary sort is set to 's', make it ascending
                dir = primary || 0
                break
              case 'o':
                temp = header.order[(primary || 0) % (c.sortReset ? 3 : 2)]
                // opposite of primary column; but resets if primary resets
                dir = temp === 0 ? 1 : temp === 1 ? 0 : 2
                break
              case 'n':
                header.count = header.count + 1
                dir = header.order[header.count % (c.sortReset ? 3 : 2)]
                break
              default:
                // ascending
                dir = 0
                break
            }
            primary = indx === 0 ? dir : primary
            group = [col, parseInt(dir, 10) || 0]
            c.sortList.push(group)
            dir = $.inArray(group[1], header.order) // fixes issue #167
            header.count = dir >= 0 ? dir : group[1] % (c.sortReset ? 3 : 2)
          }
        }
      }

      function getCachedSortType(parsers, i) {
        return parsers && parsers[i] ? parsers[i].type || '' : ''
      }

      function initSort(table, cell, event) {
        if (table.isUpdating) {
          // let any updates complete before initializing a sort
          return setTimeout(function() {
            initSort(table, cell, event)
          }, 50)
        }
        var arry,
          indx,
          i,
          col,
          order,
          s,
          $header,
          c = table.config,
          key = !event[c.sortMultiSortKey],
          $table = c.$table,
          len = c.$headers.length
        // Only call sortStart if sorting is enabled
        $table.trigger('sortStart', table)
        // get current column sort order
        cell.count = event[c.sortResetKey] ? 2 : (cell.count + 1) % (c.sortReset ? 3 : 2)
        // reset all sorts on non-current column - issue #30
        if (c.sortRestart) {
          indx = cell
          for (i = 0; i < len; i++) {
            $header = c.$headers.eq(i)
            // only reset counts on columns that weren't just clicked on and if not included in a multisort
            if (
              $header[0] !== indx &&
              (key || !$header.is('.' + ts.css.sortDesc + ',.' + ts.css.sortAsc))
            ) {
              $header[0].count = -1
            }
          }
        }
        // get current column index
        indx = parseInt($(cell).attr('data-column'), 10)
        // user only wants to sort on one column
        if (key) {
          // flush the sort list
          c.sortList = []
          if (c.sortForce !== null) {
            arry = c.sortForce
            for (col = 0; col < arry.length; col++) {
              if (arry[col][0] !== indx) {
                c.sortList.push(arry[col])
              }
            }
          }
          // add column to sort list
          order = cell.order[cell.count]
          if (order < 2) {
            c.sortList.push([indx, order])
            // add other columns if header spans across multiple
            if (cell.colSpan > 1) {
              for (col = 1; col < cell.colSpan; col++) {
                c.sortList.push([indx + col, order])
              }
            }
          }
          // multi column sorting
        } else {
          // get rid of the sortAppend before adding more - fixes issue #115 & #523
          if (c.sortAppend && c.sortList.length > 1) {
            for (col = 0; col < c.sortAppend.length; col++) {
              s = ts.isValueInArray(c.sortAppend[col][0], c.sortList)
              if (s >= 0) {
                c.sortList.splice(s, 1)
              }
            }
          }
          // the user has clicked on an already sorted column
          if (ts.isValueInArray(indx, c.sortList) >= 0) {
            // reverse the sorting direction
            for (col = 0; col < c.sortList.length; col++) {
              s = c.sortList[col]
              order = c.$headerIndexed[s[0]][0]
              if (s[0] === indx) {
                // order.count seems to be incorrect when compared to cell.count
                s[1] = order.order[cell.count]
                if (s[1] === 2) {
                  c.sortList.splice(col, 1)
                  order.count = -1
                }
              }
            }
          } else {
            // add column to sort list array
            order = cell.order[cell.count]
            if (order < 2) {
              c.sortList.push([indx, order])
              // add other columns if header spans across multiple
              if (cell.colSpan > 1) {
                for (col = 1; col < cell.colSpan; col++) {
                  c.sortList.push([indx + col, order])
                }
              }
            }
          }
        }
        if (c.sortAppend !== null) {
          arry = c.sortAppend
          for (col = 0; col < arry.length; col++) {
            if (arry[col][0] !== indx) {
              c.sortList.push(arry[col])
            }
          }
        }
        // sortBegin event triggered immediately before the sort
        $table.trigger('sortBegin', table)
        // setTimeout needed so the processing icon shows up
        setTimeout(function() {
          // set css for headers
          setHeadersCss(table)
          multisort(table)
          appendToTable(table)
          $table.trigger('sortEnd', table)
        }, 1)
      }

      // sort multiple columns
      function multisort(table) {
        /*jshint loopfunc:true */
        var i,
          k,
          num,
          col,
          sortTime,
          colMax,
          rows,
          order,
          sort,
          x,
          y,
          dir = 0,
          c = table.config,
          cts = c.textSorter || '',
          sortList = c.sortList,
          l = sortList.length,
          bl = c.$tbodies.length
        if (c.serverSideSorting || isEmptyObject(c.cache)) {
          // empty table - fixes #206/#346
          return
        }
        if (c.debug) {
          sortTime = new Date()
        }
        for (k = 0; k < bl; k++) {
          colMax = c.cache[k].colMax
          rows = c.cache[k].normalized

          rows.sort(function(a, b) {
            // rows is undefined here in IE, so don't use it!
            for (i = 0; i < l; i++) {
              col = sortList[i][0]
              order = sortList[i][1]
              // sort direction, true = asc, false = desc
              dir = order === 0

              if (c.sortStable && a[col] === b[col] && l === 1) {
                return a[c.columns].order - b[c.columns].order
              }

              // fallback to natural sort since it is more robust
              num = /n/i.test(getCachedSortType(c.parsers, col))
              if (num && c.strings[col]) {
                // sort strings in numerical columns
                if (typeof c.string[c.strings[col]] === 'boolean') {
                  num = (dir ? 1 : -1) * (c.string[c.strings[col]] ? -1 : 1)
                } else {
                  num = c.strings[col] ? c.string[c.strings[col]] || 0 : 0
                }
                // fall back to built-in numeric sort
                // var sort = $.tablesorter['sort' + s](table, a[c], b[c], c, colMax[c], dir);
                sort = c.numberSorter
                  ? c.numberSorter(a[col], b[col], dir, colMax[col], table)
                  : ts['sortNumeric' + (dir ? 'Asc' : 'Desc')](
                      a[col],
                      b[col],
                      num,
                      colMax[col],
                      col,
                      table
                    )
              } else {
                // set a & b depending on sort direction
                x = dir ? a : b
                y = dir ? b : a
                // text sort function
                if (typeof cts === 'function') {
                  // custom OVERALL text sorter
                  sort = cts(x[col], y[col], dir, col, table)
                } else if (typeof cts === 'object' && cts.hasOwnProperty(col)) {
                  // custom text sorter for a SPECIFIC COLUMN
                  sort = cts[col](x[col], y[col], dir, col, table)
                } else {
                  // fall back to natural sort
                  sort = ts['sortNatural' + (dir ? 'Asc' : 'Desc')](a[col], b[col], col, table, c)
                }
              }
              if (sort) {
                return sort
              }
            }
            return a[c.columns].order - b[c.columns].order
          })
        }
        if (c.debug) {
          benchmark('Sorting on ' + sortList.toString() + ' and dir ' + order + ' time', sortTime)
        }
      }

      function resortComplete(c, callback) {
        if (c.table.isUpdating) {
          c.$table.trigger('updateComplete', c.table)
        }
        if ($.isFunction(callback)) {
          callback(c.table)
        }
      }

      function checkResort(c, resort, callback) {
        var sl = $.isArray(resort) ? resort : c.sortList,
          // if no resort parameter is passed, fallback to config.resort (true by default)
          resrt = typeof resort === 'undefined' ? c.resort : resort
        // don't try to resort if the table is still processing
        // this will catch spamming of the updateCell method
        if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) {
          if (sl.length) {
            c.$table.trigger('sorton', [
              sl,
              function() {
                resortComplete(c, callback)
              },
              true
            ])
          } else {
            c.$table.trigger('sortReset', [
              function() {
                resortComplete(c, callback)
                ts.applyWidget(c.table, false)
              }
            ])
          }
        } else {
          resortComplete(c, callback)
          ts.applyWidget(c.table, false)
        }
      }

      function bindMethods(table) {
        var c = table.config,
          $table = c.$table,
          events = (
            'sortReset update updateRows updateCell updateAll addRows updateComplete sorton appendCache ' +
            'updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave '
          )
            .split(' ')
            .join(c.namespace + ' ')
        // apply easy methods that trigger bound events
        $table
          .unbind(events.replace(/\s+/g, ' '))
          .bind('sortReset' + c.namespace, function(e, callback) {
            e.stopPropagation()
            c.sortList = []
            setHeadersCss(table)
            multisort(table)
            appendToTable(table)
            if ($.isFunction(callback)) {
              callback(table)
            }
          })
          .bind('updateAll' + c.namespace, function(e, resort, callback) {
            e.stopPropagation()
            table.isUpdating = true
            ts.refreshWidgets(table, true, true)
            buildHeaders(table)
            ts.bindEvents(table, c.$headers, true)
            bindMethods(table)
            commonUpdate(table, resort, callback)
          })
          .bind('update' + c.namespace + ' updateRows' + c.namespace, function(
            e,
            resort,
            callback
          ) {
            e.stopPropagation()
            table.isUpdating = true
            // update sorting (if enabled/disabled)
            updateHeader(table)
            commonUpdate(table, resort, callback)
          })
          .bind('updateCell' + c.namespace, function(e, cell, resort, callback) {
            e.stopPropagation()
            table.isUpdating = true
            $table.find(c.selectorRemove).remove()
            // get position from the dom
            var t,
              row,
              icell,
              cache,
              $tb = c.$tbodies,
              $cell = $(cell),
              // update cache - format: function(s, table, cell, cellIndex)
              // no closest in jQuery v1.2.6 - tbdy = $tb.index( $(cell).closest('tbody') ),$row = $(cell).closest('tr');
              tbdy = $tb.index(
                $.fn.closest ? $cell.closest('tbody') : $cell.parents('tbody').filter(':first')
              ),
              tbcache = c.cache[tbdy],
              $row = $.fn.closest ? $cell.closest('tr') : $cell.parents('tr').filter(':first')
            cell = $cell[0] // in case cell is a jQuery object
            // tbody may not exist if update is initialized while tbody is removed for processing
            if ($tb.length && tbdy >= 0) {
              row = $tb
                .eq(tbdy)
                .find('tr')
                .index($row)
              cache = tbcache.normalized[row]
              icell = $cell.index()
              t = getParsedText(c, cell, icell)
              cache[icell] = t
              cache[c.columns].$row = $row
              if ((c.parsers[icell].type || '').toLowerCase() === 'numeric') {
                // update column max value (ignore sign)
                tbcache.colMax[icell] = Math.max(Math.abs(t) || 0, tbcache.colMax[icell] || 0)
              }
              t = resort !== 'undefined' ? resort : c.resort
              if (t !== false) {
                // widgets will be reapplied
                checkResort(c, t, callback)
              } else {
                // don't reapply widgets is resort is false, just in case it causes
                // problems with element focus
                if ($.isFunction(callback)) {
                  callback(table)
                }
                c.$table.trigger('updateComplete', c.table)
              }
            }
          })
          .bind('addRows' + c.namespace, function(e, $row, resort, callback) {
            e.stopPropagation()
            table.isUpdating = true
            if (isEmptyObject(c.cache)) {
              // empty table, do an update instead - fixes #450
              updateHeader(table)
              commonUpdate(table, resort, callback)
            } else {
              $row = $($row).attr('role', 'row') // make sure we're using a jQuery object
              var i,
                j,
                l,
                rowData,
                cells,
                rows = $row.filter('tr').length,
                tbdy = c.$tbodies.index($row.parents('tbody').filter(':first'))
              // fixes adding rows to an empty table - see issue #179
              if (!(c.parsers && c.parsers.length)) {
                buildParserCache(table)
              }
              // add each row
              for (i = 0; i < rows; i++) {
                l = $row[i].cells.length
                cells = []
                rowData = {
                  child: [],
                  $row: $row.eq(i),
                  order: c.cache[tbdy].normalized.length
                }
                // add each cell
                for (j = 0; j < l; j++) {
                  cells[j] = getParsedText(c, $row[i].cells[j], j)
                  if ((c.parsers[j].type || '').toLowerCase() === 'numeric') {
                    // update column max value (ignore sign)
                    c.cache[tbdy].colMax[j] = Math.max(
                      Math.abs(cells[j]) || 0,
                      c.cache[tbdy].colMax[j] || 0
                    )
                  }
                }
                // add the row data to the end
                cells.push(rowData)
                // update cache
                c.cache[tbdy].normalized.push(cells)
              }
              // resort using current settings
              checkResort(c, resort, callback)
            }
          })
          .bind('updateComplete' + c.namespace, function() {
            table.isUpdating = false
          })
          .bind('sorton' + c.namespace, function(e, list, callback, init) {
            var c = table.config
            e.stopPropagation()
            $table.trigger('sortStart', this)
            // update header count index
            updateHeaderSortCount(table, list)
            // set css for headers
            setHeadersCss(table)
            // fixes #346
            if (c.delayInit && isEmptyObject(c.cache)) {
              buildCache(table)
            }
            $table.trigger('sortBegin', this)
            // sort the table and append it to the dom
            multisort(table)
            appendToTable(table, init)
            $table.trigger('sortEnd', this)
            ts.applyWidget(table)
            if ($.isFunction(callback)) {
              callback(table)
            }
          })
          .bind('appendCache' + c.namespace, function(e, callback, init) {
            e.stopPropagation()
            appendToTable(table, init)
            if ($.isFunction(callback)) {
              callback(table)
            }
          })
          .bind('updateCache' + c.namespace, function(e, callback) {
            // rebuild parsers
            if (!(c.parsers && c.parsers.length)) {
              buildParserCache(table)
            }
            // rebuild the cache map
            buildCache(table)
            if ($.isFunction(callback)) {
              callback(table)
            }
          })
          .bind('applyWidgetId' + c.namespace, function(e, id) {
            e.stopPropagation()
            ts.getWidgetById(id).format(table, c, c.widgetOptions)
          })
          .bind('applyWidgets' + c.namespace, function(e, init) {
            e.stopPropagation()
            // apply widgets
            ts.applyWidget(table, init)
          })
          .bind('refreshWidgets' + c.namespace, function(e, all, dontapply) {
            e.stopPropagation()
            ts.refreshWidgets(table, all, dontapply)
          })
          .bind('destroy' + c.namespace, function(e, c, cb) {
            e.stopPropagation()
            ts.destroy(table, c, cb)
          })
          .bind('resetToLoadState' + c.namespace, function() {
            // remove all widgets
            ts.removeWidget(table, true, false)
            // restore original settings; this clears out current settings, but does not clear
            // values saved to storage.
            c = $.extend(true, ts.defaults, c.originalSettings)
            table.hasInitialized = false
            // setup the entire table again
            ts.setup(table, c)
          })
      }

      /* public methods */
      ts.construct = function(settings) {
        return this.each(function() {
          var table = this,
            // merge & extend config options
            c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods)
          // save initial settings
          c.originalSettings = settings
          // create a table from data (build table widget)
          if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') {
            // return the table (in case the original target is the table's container)
            ts.buildTable(table, c)
          } else {
            ts.setup(table, c)
          }
        })
      }

      ts.setup = function(table, c) {
        // if no thead or tbody, or tablesorter is already present, quit
        if (!table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true) {
          return c.debug
            ? log(
                'ERROR: stopping initialization! No table, thead, tbody or tablesorter has already been initialized'
              )
            : ''
        }

        var k = '',
          $table = $(table),
          m = $.metadata
        // initialization flag
        table.hasInitialized = false
        // table is being processed flag
        table.isProcessing = true
        // make sure to store the config object
        table.config = c
        // save the settings where they read
        $.data(table, 'tablesorter', c)
        if (c.debug) {
          $.data(table, 'startoveralltimer', new Date())
        }

        // removing this in version 3 (only supports jQuery 1.7+)
        c.supportsDataObject = (function(version) {
          version[0] = parseInt(version[0], 10)
          return version[0] > 1 || (version[0] === 1 && parseInt(version[1], 10) >= 4)
        })($.fn.jquery.split('.'))
        // digit sort text location; keeping max+/- for backwards compatibility
        c.string = {
          max: 1,
          min: -1,
          emptymin: 1,
          emptymax: -1,
          zero: 0,
          none: 0,
          null: 0,
          top: true,
          bottom: false
        }
        // ensure case insensitivity
        c.emptyTo = c.emptyTo.toLowerCase()
        c.stringTo = c.stringTo.toLowerCase()
        // add table theme class only if there isn't already one there
        if (!/tablesorter\-/.test($table.attr('class'))) {
          k = c.theme !== '' ? ' tablesorter-' + c.theme : ''
        }
        c.table = table
        c.$table = $table.addClass(ts.css.table + ' ' + c.tableClass + k).attr('role', 'grid')
        c.$headers = $table.find(c.selectorHeaders)

        // give the table a unique id, which will be used in namespace binding
        if (!c.namespace) {
          c.namespace =
            '.tablesorter' +
            Math.random()
              .toString(16)
              .slice(2)
        } else {
          // make sure namespace starts with a period & doesn't have weird characters
          c.namespace = '.' + c.namespace.replace(/\W/g, '')
        }

        c.$table
          .children()
          .children('tr')
          .attr('role', 'row')
        c.$tbodies = $table.children('tbody:not(.' + c.cssInfoBlock + ')').attr({
          'aria-live': 'polite',
          'aria-relevant': 'all'
        })
        if (c.$table.children('caption').length) {
          k = c.$table.children('caption')[0]
          if (!k.id) {
            k.id = c.namespace.slice(1) + 'caption'
          }
          c.$table.attr('aria-labelledby', k.id)
        }
        c.widgetInit = {} // keep a list of initialized widgets
        // change textExtraction via data-attribute
        c.textExtraction = c.$table.attr('data-text-extraction') || c.textExtraction || 'basic'
        // build headers
        buildHeaders(table)
        // fixate columns if the users supplies the fixedWidth option
        // do this after theme has been applied
        ts.fixColumnWidth(table)
        // add widget options before parsing (e.g. grouping widget has parser settings)
        ts.applyWidgetOptions(table, c)
        // try to auto detect column type, and store in tables config
        buildParserCache(table)
        // start total row count at zero
        c.totalRows = 0
        // build the cache for the tbody cells
        // delayInit will delay building the cache until the user starts a sort
        if (!c.delayInit) {
          buildCache(table)
        }
        // bind all header events and methods
        ts.bindEvents(table, c.$headers, true)
        bindMethods(table)
        // get sort list from jQuery data or metadata
        // in jQuery < 1.4, an error occurs when calling $table.data()
        if (c.supportsDataObject && typeof $table.data().sortlist !== 'undefined') {
          c.sortList = $table.data().sortlist
        } else if (m && ($table.metadata() && $table.metadata().sortlist)) {
          c.sortList = $table.metadata().sortlist
        }
        // apply widget init code
        ts.applyWidget(table, true)
        // if user has supplied a sort list to constructor
        if (c.sortList.length > 0) {
          $table.trigger('sorton', [c.sortList, {}, !c.initWidgets, true])
        } else {
          setHeadersCss(table)
          if (c.initWidgets) {
            // apply widget format
            ts.applyWidget(table, false)
          }
        }

        // show processesing icon
        if (c.showProcessing) {
          $table
            .unbind('sortBegin' + c.namespace + ' sortEnd' + c.namespace)
            .bind('sortBegin' + c.namespace + ' sortEnd' + c.namespace, function(e) {
              clearTimeout(c.processTimer)
              ts.isProcessing(table)
              if (e.type === 'sortBegin') {
                c.processTimer = setTimeout(function() {
                  ts.isProcessing(table, true)
                }, 500)
              }
            })
        }

        // initialized
        table.hasInitialized = true
        table.isProcessing = false
        if (c.debug) {
          ts.benchmark('Overall initialization time', $.data(table, 'startoveralltimer'))
        }
        $table.trigger('tablesorter-initialized', table)
        if (typeof c.initialized === 'function') {
          c.initialized(table)
        }
      }

      // automatically add a colgroup with col elements set to a percentage width
      ts.fixColumnWidth = function(table) {
        table = $(table)[0]
        var overallWidth,
          percent,
          $tbodies,
          len,
          index,
          c = table.config,
          colgroup = c.$table.children('colgroup')
        // remove plugin-added colgroup, in case we need to refresh the widths
        if (colgroup.length && colgroup.hasClass(ts.css.colgroup)) {
          colgroup.remove()
        }
        if (c.widthFixed && c.$table.children('colgroup').length === 0) {
          colgroup = $('<colgroup class="' + ts.css.colgroup + '">')
          overallWidth = c.$table.width()
          // only add col for visible columns - fixes #371
          $tbodies = c.$tbodies.find('tr:first').children(':visible') //.each(function()
          len = $tbodies.length
          for (index = 0; index < len; index++) {
            percent = parseInt(($tbodies.eq(index).width() / overallWidth) * 1000, 10) / 10 + '%'
            colgroup.append($('<col>').css('width', percent))
          }
          c.$table.prepend(colgroup)
        }
      }

      ts.getColumnData = function(table, obj, indx, getCell, $headers) {
        if (typeof obj === 'undefined' || obj === null) {
          return
        }
        table = $(table)[0]
        var $h,
          k,
          c = table.config,
          $cells = $headers || c.$headers,
          // c.$headerIndexed is not defined initially
          $cell =
            (c.$headerIndexed && c.$headerIndexed[indx]) ||
            $cells.filter('[data-column="' + indx + '"]:last')
        if (obj[indx]) {
          return getCell ? obj[indx] : obj[$cells.index($cell)]
        }
        for (k in obj) {
          if (typeof k === 'string') {
            $h = $cell
              // header cell with class/id
              .filter(k)
              // find elements within the header cell with cell/id
              .add($cell.find(k))
            if ($h.length) {
              return obj[k]
            }
          }
        }
        return
      }

      // computeTableHeaderCellIndexes from:
      // http://www.javascripttoolbox.com/lib/table/examples.php
      // http://www.javascripttoolbox.com/temp/table_cellindex.html
      ts.computeColumnIndex = function(trs) {
        var i,
          j,
          k,
          l,
          $cell,
          cell,
          cells,
          rowIndex,
          cellId,
          rowSpan,
          colSpan,
          firstAvailCol,
          matrix = [],
          matrixrow = [],
          lookup = {}
        for (i = 0; i < trs.length; i++) {
          cells = trs[i].cells
          for (j = 0; j < cells.length; j++) {
            cell = cells[j]
            $cell = $(cell)
            rowIndex = cell.parentNode.rowIndex
            cellId = rowIndex + '-' + $cell.index()
            rowSpan = cell.rowSpan || 1
            colSpan = cell.colSpan || 1
            if (typeof matrix[rowIndex] === 'undefined') {
              matrix[rowIndex] = []
            }
            // Find first available column in the first row
            for (k = 0; k < matrix[rowIndex].length + 1; k++) {
              if (typeof matrix[rowIndex][k] === 'undefined') {
                firstAvailCol = k
                break
              }
            }
            lookup[cellId] = firstAvailCol
            // add data-column
            $cell.attr({ 'data-column': firstAvailCol }) // 'data-row' : rowIndex
            for (k = rowIndex; k < rowIndex + rowSpan; k++) {
              if (typeof matrix[k] === 'undefined') {
                matrix[k] = []
              }
              matrixrow = matrix[k]
              for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
                matrixrow[l] = 'x'
              }
            }
          }
        }
        return matrixrow.length
      }

      // *** Process table ***
      // add processing indicator
      ts.isProcessing = function(table, toggle, $ths) {
        table = $(table)
        var c = table[0].config,
          // default to all headers
          $h = $ths || table.find('.' + ts.css.header)
        if (toggle) {
          // don't use sortList if custom $ths used
          if (typeof $ths !== 'undefined' && c.sortList.length > 0) {
            // get headers from the sortList
            $h = $h.filter(function() {
              // get data-column from attr to keep  compatibility with jQuery 1.2.6
              return this.sortDisabled
                ? false
                : ts.isValueInArray(parseFloat($(this).attr('data-column')), c.sortList) >= 0
            })
          }
          table.add($h).addClass(ts.css.processing + ' ' + c.cssProcessing)
        } else {
          table.add($h).removeClass(ts.css.processing + ' ' + c.cssProcessing)
        }
      }

      // detach tbody but save the position
      // don't use tbody because there are portions that look for a tbody index (updateCell)
      ts.processTbody = function(table, $tb, getIt) {
        table = $(table)[0]
        var holdr
        if (getIt) {
          table.isProcessing = true
          $tb.before('<span class="tablesorter-savemyplace"/>')
          holdr = $.fn.detach ? $tb.detach() : $tb.remove()
          return holdr
        }
        holdr = $(table).find('span.tablesorter-savemyplace')
        $tb.insertAfter(holdr)
        holdr.remove()
        table.isProcessing = false
      }

      ts.clearTableBody = function(table) {
        $(table)[0]
          .config.$tbodies.children()
          .detach()
      }

      ts.bindEvents = function(table, $headers, core) {
        table = $(table)[0]
        var t,
          downTarget = null,
          c = table.config
        if (core !== true) {
          $headers.addClass(c.namespace.slice(1) + '_extra_headers')
          t = $.fn.closest ? $headers.closest('table')[0] : $headers.parents('table')[0]
          if (t && t.nodeName === 'TABLE' && t !== table) {
            $(t).addClass(c.namespace.slice(1) + '_extra_table')
          }
        }
        t = (c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ')
          .replace(/\s+/g, ' ')
          .split(' ')
          .join(c.namespace + ' ')
        // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
        $headers
          // http://stackoverflow.com/questions/5312849/jquery-find-self;
          .find(c.selectorSort)
          .add($headers.filter(c.selectorSort))
          .unbind(t)
          .bind(t, function(e, external) {
            var cell,
              temp,
              $target = $(e.target),
              // wrap event type in spaces, so the match doesn't trigger on inner words
              type = ' ' + e.type + ' '
            // only recognize left clicks
            if (
              ((e.which || e.button) !== 1 &&
                !type.match(' ' + c.pointerClick + ' | sort | keyup ')) ||
              // allow pressing enter
              (type === ' keyup ' && e.which !== 13) ||
              // allow triggering a click event (e.which is undefined) & ignore physical clicks
              (type.match(' ' + c.pointerClick + ' ') && typeof e.which !== 'undefined')
            ) {
              return
            }
            // ignore mouseup if mousedown wasn't on the same target
            if (
              type.match(' ' + c.pointerUp + ' ') &&
              downTarget !== e.target &&
              external !== true
            ) {
              return
            }
            // set target on mousedown
            if (type.match(' ' + c.pointerDown + ' ')) {
              downTarget = e.target
              // preventDefault needed or jQuery v1.3.2 and older throws an
              // "Uncaught TypeError: handler.apply is not a function" error
              temp = $target.jquery.split('.')
              if (temp[0] === '1' && temp[1] < 4) {
                e.preventDefault()
              }
              return
            }
            downTarget = null
            // prevent sort being triggered on form elements
            if (
              /(input|select|button|textarea)/i.test(e.target.nodeName) ||
              // nosort class name, or elements within a nosort container
              $target.hasClass(c.cssNoSort) ||
              $target.parents('.' + c.cssNoSort).length > 0 ||
              // elements within a button
              $target.parents('button').length > 0
            ) {
              return !c.cancelSelection
            }
            if (c.delayInit && isEmptyObject(c.cache)) {
              buildCache(table)
            }
            // jQuery v1.2.6 doesn't have closest()
            cell = $.fn.closest
              ? $(this).closest('th, td')[0]
              : /TH|TD/.test(this.nodeName)
              ? this
              : $(this).parents('th, td')[0]
            // reference original table headers and find the same cell
            cell = c.$headers[$headers.index(cell)]
            if (!cell.sortDisabled) {
              initSort(table, cell, e)
            }
          })
        if (c.cancelSelection) {
          // cancel selection
          $headers
            .attr('unselectable', 'on')
            .bind('selectstart', false)
            .css({
              'user-select': 'none',
              MozUserSelect: 'none' // not needed for jQuery 1.8+
            })
        }
      }

      // restore headers
      ts.restoreHeaders = function(table) {
        var index,
          $cell,
          c = $(table)[0].config,
          $headers = c.$table.find(c.selectorHeaders),
          len = $headers.length
        // don't use c.$headers here in case header cells were swapped
        for (index = 0; index < len; index++) {
          // c.$table.find(c.selectorHeaders).each(function(i){
          $cell = $headers.eq(index)
          // only restore header cells if it is wrapped
          // because this is also used by the updateAll method
          if ($cell.find('.' + ts.css.headerIn).length) {
            $cell.html(c.headerContent[index])
          }
        }
      }

      ts.destroy = function(table, removeClasses, callback) {
        table = $(table)[0]
        if (!table.hasInitialized) {
          return
        }
        // remove all widgets
        ts.removeWidget(table, true, false)
        var events,
          $t = $(table),
          c = table.config,
          $h = $t.find('thead:first'),
          $r = $h
            .find('tr.' + ts.css.headerRow)
            .removeClass(ts.css.headerRow + ' ' + c.cssHeaderRow),
          $f = $t.find('tfoot:first > tr').children('th, td')
        if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) {
          // reapply uitheme classes, in case we want to maintain appearance
          $t.trigger('applyWidgetId', ['uitheme'])
          $t.trigger('applyWidgetId', ['zebra'])
        }
        // remove widget added rows, just in case
        $h.find('tr')
          .not($r)
          .remove()
        // disable tablesorter
        events =
          'sortReset update updateAll updateRows updateCell addRows updateComplete sorton appendCache updateCache ' +
          'applyWidgetId applyWidgets refreshWidgets destroy mouseup mouseleave keypress sortBegin sortEnd resetToLoadState '
            .split(' ')
            .join(c.namespace + ' ')
        $t.removeData('tablesorter').unbind(events.replace(/\s+/g, ' '))
        c.$headers
          .add($f)
          .removeClass(
            [
              ts.css.header,
              c.cssHeader,
              c.cssAsc,
              c.cssDesc,
              ts.css.sortAsc,
              ts.css.sortDesc,
              ts.css.sortNone
            ].join(' ')
          )
          .removeAttr('data-column')
          .removeAttr('aria-label')
          .attr('aria-disabled', 'true')
        $r.find(c.selectorSort).unbind(
          'mousedown mouseup keypress '
            .split(' ')
            .join(c.namespace + ' ')
            .replace(/\s+/g, ' ')
        )
        ts.restoreHeaders(table)
        $t.toggleClass(
          ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme,
          removeClasses === false
        )
        // clear flag in case the plugin is initialized again
        table.hasInitialized = false
        delete table.config.cache
        if (typeof callback === 'function') {
          callback(table)
        }
      }

      // *** sort functions ***
      // regex used in natural sort
      ts.regex = {
        chunk: /(^([+\-]?(?:0|[1-9]\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi, // chunk/tokenize numbers & letters
        chunks: /(^\\0|\\0$)/, // replace chunks @ ends
        hex: /^0x[0-9a-f]+$/i // hex
      }

      // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
      // this function will only accept strings, or you'll see 'TypeError: undefined is not a function'
      // I could add a = a.toString(); b = b.toString(); but it'll slow down the sort overall
      ts.sortNatural = function(a, b) {
        if (a === b) {
          return 0
        }
        var xN,
          xD,
          yN,
          yD,
          xF,
          yF,
          i,
          mx,
          r = ts.regex
        // first try and sort Hex codes
        if (r.hex.test(b)) {
          xD = parseInt(a.match(r.hex), 16)
          yD = parseInt(b.match(r.hex), 16)
          if (xD < yD) {
            return -1
          }
          if (xD > yD) {
            return 1
          }
        }
        // chunk/tokenize
        xN = a
          .replace(r.chunk, '\\0$1\\0')
          .replace(r.chunks, '')
          .split('\\0')
        yN = b
          .replace(r.chunk, '\\0$1\\0')
          .replace(r.chunks, '')
          .split('\\0')
        mx = Math.max(xN.length, yN.length)
        // natural sorting through split numeric strings and default strings
        for (i = 0; i < mx; i++) {
          // find floats not starting with '0', string or 0 if not defined
          xF = isNaN(xN[i]) ? xN[i] || 0 : parseFloat(xN[i]) || 0
          yF = isNaN(yN[i]) ? yN[i] || 0 : parseFloat(yN[i]) || 0
          // handle numeric vs string comparison - number < string - (Kyle Adams)
          if (isNaN(xF) !== isNaN(yF)) {
            return isNaN(xF) ? 1 : -1
          }
          // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
          if (typeof xF !== typeof yF) {
            xF += ''
            yF += ''
          }
          if (xF < yF) {
            return -1
          }
          if (xF > yF) {
            return 1
          }
        }
        return 0
      }

      ts.sortNaturalAsc = function(a, b, col, table, c) {
        if (a === b) {
          return 0
        }
        var e = c.string[c.empties[col] || c.emptyTo]
        if (a === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1
        }
        if (b === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1
        }
        return ts.sortNatural(a, b)
      }

      ts.sortNaturalDesc = function(a, b, col, table, c) {
        if (a === b) {
          return 0
        }
        var e = c.string[c.empties[col] || c.emptyTo]
        if (a === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1
        }
        if (b === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1
        }
        return ts.sortNatural(b, a)
      }

      // basic alphabetical sort
      ts.sortText = function(a, b) {
        return a > b ? 1 : a < b ? -1 : 0
      }

      // return text string value by adding up ascii value
      // so the text is somewhat sorted when using a digital sort
      // this is NOT an alphanumeric sort
      ts.getTextValue = function(a, num, mx) {
        if (mx) {
          // make sure the text value is greater than the max numerical value (mx)
          var i,
            l = a ? a.length : 0,
            n = mx + num
          for (i = 0; i < l; i++) {
            n += a.charCodeAt(i)
          }
          return num * n
        }
        return 0
      }

      ts.sortNumericAsc = function(a, b, num, mx, col, table) {
        if (a === b) {
          return 0
        }
        var c = table.config,
          e = c.string[c.empties[col] || c.emptyTo]
        if (a === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? -1 : 1) : -e || -1
        }
        if (b === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? 1 : -1) : e || 1
        }
        if (isNaN(a)) {
          a = ts.getTextValue(a, num, mx)
        }
        if (isNaN(b)) {
          b = ts.getTextValue(b, num, mx)
        }
        return a - b
      }

      ts.sortNumericDesc = function(a, b, num, mx, col, table) {
        if (a === b) {
          return 0
        }
        var c = table.config,
          e = c.string[c.empties[col] || c.emptyTo]
        if (a === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? -1 : 1) : e || 1
        }
        if (b === '' && e !== 0) {
          return typeof e === 'boolean' ? (e ? 1 : -1) : -e || -1
        }
        if (isNaN(a)) {
          a = ts.getTextValue(a, num, mx)
        }
        if (isNaN(b)) {
          b = ts.getTextValue(b, num, mx)
        }
        return b - a
      }

      ts.sortNumeric = function(a, b) {
        return a - b
      }

      // used when replacing accented characters during sorting
      ts.characterEquivalents = {
        a: '\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5', // áàâãäąå
        A: '\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5', // ÁÀÂÃÄĄÅ
        c: '\u00e7\u0107\u010d', // çćč
        C: '\u00c7\u0106\u010c', // ÇĆČ
        e: '\u00e9\u00e8\u00ea\u00eb\u011b\u0119', // éèêëěę
        E: '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÉÈÊËĚĘ
        i: '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // íìİîïı
        I: '\u00cd\u00cc\u0130\u00ce\u00cf', // ÍÌİÎÏ
        o: '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöō
        O: '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÓÒÔÕÖŌ
        ss: '\u00df', // ß (s sharp)
        SS: '\u1e9e', // ẞ (Capital sharp s)
        u: '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů
        U: '\u00da\u00d9\u00db\u00dc\u016e' // ÚÙÛÜŮ
      }
      ts.replaceAccents = function(s) {
        var a,
          acc = '[',
          eq = ts.characterEquivalents
        if (!ts.characterRegex) {
          ts.characterRegexArray = {}
          for (a in eq) {
            if (typeof a === 'string') {
              acc += eq[a]
              ts.characterRegexArray[a] = new RegExp('[' + eq[a] + ']', 'g')
            }
          }
          ts.characterRegex = new RegExp(acc + ']')
        }
        if (ts.characterRegex.test(s)) {
          for (a in eq) {
            if (typeof a === 'string') {
              s = s.replace(ts.characterRegexArray[a], a)
            }
          }
        }
        return s
      }

      // *** utilities ***
      ts.isValueInArray = function(column, arry) {
        var indx,
          len = arry.length
        for (indx = 0; indx < len; indx++) {
          if (arry[indx][0] === column) {
            return indx
          }
        }
        return -1
      }

      ts.addParser = function(parser) {
        var i,
          l = ts.parsers.length,
          a = true
        for (i = 0; i < l; i++) {
          if (ts.parsers[i].id.toLowerCase() === parser.id.toLowerCase()) {
            a = false
          }
        }
        if (a) {
          ts.parsers.push(parser)
        }
      }

      // Use it to add a set of methods to table.config which will be available for all tables.
      // This should be done before table initialization
      ts.addInstanceMethods = function(methods) {
        $.extend(ts.instanceMethods, methods)
      }

      ts.getParserById = function(name) {
        /*jshint eqeqeq:false */
        if (name == 'false') {
          return false
        }
        var i,
          l = ts.parsers.length
        for (i = 0; i < l; i++) {
          if (ts.parsers[i].id.toLowerCase() === name.toString().toLowerCase()) {
            return ts.parsers[i]
          }
        }
        return false
      }

      ts.addWidget = function(widget) {
        ts.widgets.push(widget)
      }

      ts.hasWidget = function(table, name) {
        table = $(table)
        return (table.length && table[0].config && table[0].config.widgetInit[name]) || false
      }

      ts.getWidgetById = function(name) {
        var i,
          w,
          l = ts.widgets.length
        for (i = 0; i < l; i++) {
          w = ts.widgets[i]
          if (w && w.hasOwnProperty('id') && w.id.toLowerCase() === name.toLowerCase()) {
            return w
          }
        }
      }

      ts.applyWidgetOptions = function(table, c) {
        var indx,
          widget,
          len = c.widgets.length,
          wo = c.widgetOptions
        if (len) {
          for (indx = 0; indx < len; indx++) {
            widget = ts.getWidgetById(c.widgets[indx])
            if (widget && 'options' in widget) {
              wo = table.config.widgetOptions = $.extend(true, {}, widget.options, wo)
            }
          }
        }
      }

      ts.applyWidget = function(table, init, callback) {
        table = $(table)[0] // in case this is called externally
        var indx,
          len,
          name,
          c = table.config,
          wo = c.widgetOptions,
          tableClass = ' ' + c.table.className + ' ',
          widgets = [],
          time,
          time2,
          w,
          wd
        // prevent numerous consecutive widget applications
        if (
          init !== false &&
          table.hasInitialized &&
          (table.isApplyingWidgets || table.isUpdating)
        ) {
          return
        }
        if (c.debug) {
          time = new Date()
        }
        // look for widgets to apply from in table class
        // stop using \b otherwise this matches 'ui-widget-content' & adds 'content' widget
        wd = new RegExp('\\s' + c.widgetClass.replace(/\{name\}/i, '([\\w-]+)') + '\\s', 'g')
        if (tableClass.match(wd)) {
          // extract out the widget id from the table class (widget id's can include dashes)
          w = tableClass.match(wd)
          if (w) {
            len = w.length
            for (indx = 0; indx < len; indx++) {
              c.widgets.push(w[indx].replace(wd, '$1'))
            }
          }
        }
        if (c.widgets.length) {
          table.isApplyingWidgets = true
          // ensure unique widget ids
          c.widgets = $.grep(c.widgets, function(v, k) {
            return $.inArray(v, c.widgets) === k
          })
          name = c.widgets || []
          len = name.length
          // build widget array & add priority as needed
          for (indx = 0; indx < len; indx++) {
            wd = ts.getWidgetById(name[indx])
            if (wd && wd.id) {
              // set priority to 10 if not defined
              if (!wd.priority) {
                wd.priority = 10
              }
              widgets[indx] = wd
            }
          }
          // sort widgets by priority
          widgets.sort(function(a, b) {
            return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1
          })
          // add/update selected widgets
          len = widgets.length
          for (indx = 0; indx < len; indx++) {
            if (widgets[indx]) {
              if (init || !c.widgetInit[widgets[indx].id]) {
                // set init flag first to prevent calling init more than once (e.g. pager)
                c.widgetInit[widgets[indx].id] = true
                if (table.hasInitialized) {
                  // don't reapply widget options on tablesorter init
                  ts.applyWidgetOptions(table, c)
                }
                if ('init' in widgets[indx]) {
                  if (c.debug) {
                    time2 = new Date()
                  }
                  widgets[indx].init(table, widgets[indx], c, wo)
                  if (c.debug) {
                    ts.benchmark('Initializing ' + widgets[indx].id + ' widget', time2)
                  }
                }
              }
              if (!init && 'format' in widgets[indx]) {
                if (c.debug) {
                  time2 = new Date()
                }
                widgets[indx].format(table, c, wo, false)
                if (c.debug) {
                  ts.benchmark(
                    (init ? 'Initializing ' : 'Applying ') + widgets[indx].id + ' widget',
                    time2
                  )
                }
              }
            }
          }
          // callback executed on init only
          if (!init && typeof callback === 'function') {
            callback(table)
          }
        }
        setTimeout(function() {
          table.isApplyingWidgets = false
          $.data(table, 'lastWidgetApplication', new Date())
        }, 0)
        if (c.debug) {
          w = c.widgets.length
          benchmark(
            'Completed ' +
              (init === true ? 'initializing ' : 'applying ') +
              w +
              ' widget' +
              (w !== 1 ? 's' : ''),
            time
          )
        }
      }

      ts.removeWidget = function(table, name, refreshing) {
        table = $(table)[0]
        var i,
          widget,
          indx,
          len,
          c = table.config
        // if name === true, add all widgets from $.tablesorter.widgets
        if (name === true) {
          name = []
          len = ts.widgets.length
          for (indx = 0; indx < len; indx++) {
            widget = ts.widgets[indx]
            if (widget && widget.id) {
              name.push(widget.id)
            }
          }
        } else {
          // name can be either an array of widgets names,
          // or a space/comma separated list of widget names
          name = ($.isArray(name) ? name.join(',') : name || '').toLowerCase().split(/[\s,]+/)
        }
        len = name.length
        for (i = 0; i < len; i++) {
          widget = ts.getWidgetById(name[i])
          indx = $.inArray(name[i], c.widgets)
          if (widget && 'remove' in widget) {
            if (c.debug && indx >= 0) {
              log('Removing "' + name[i] + '" widget')
            }
            widget.remove(table, c, c.widgetOptions, refreshing)
            c.widgetInit[name[i]] = false
          }
          // don't remove the widget from config.widget if refreshing
          if (indx >= 0 && refreshing !== true) {
            c.widgets.splice(indx, 1)
          }
        }
      }

      ts.refreshWidgets = function(table, doAll, dontapply) {
        table = $(table)[0] // see issue #243
        var indx,
          c = table.config,
          cw = c.widgets,
          widgets = ts.widgets,
          len = widgets.length,
          list = [],
          callback = function(table) {
            $(table).trigger('refreshComplete')
          }
        // remove widgets not defined in config.widgets, unless doAll is true
        for (indx = 0; indx < len; indx++) {
          if (widgets[indx] && widgets[indx].id && (doAll || $.inArray(widgets[indx].id, cw) < 0)) {
            list.push(widgets[indx].id)
          }
        }
        ts.removeWidget(table, list.join(','), true)
        if (dontapply !== true) {
          // call widget init if
          ts.applyWidget(table, doAll || false, callback)
          if (doAll) {
            // apply widget format
            ts.applyWidget(table, false, callback)
          }
        } else {
          callback(table)
        }
      }

      ts.getColumnText = function(table, column, callback) {
        table = $(table)[0]
        var tbodyIndex,
          rowIndex,
          cache,
          row,
          tbodyLen,
          rowLen,
          raw,
          parsed,
          $cell,
          result,
          hasCallback = typeof callback === 'function',
          allColumns = column === 'all',
          data = { raw: [], parsed: [], $cell: [] },
          c = table.config
        if (!isEmptyObject(c)) {
          tbodyLen = c.$tbodies.length
          for (tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++) {
            cache = c.cache[tbodyIndex].normalized
            rowLen = cache.length
            for (rowIndex = 0; rowIndex < rowLen; rowIndex++) {
              result = true
              row = cache[rowIndex]
              parsed = allColumns ? row.slice(0, c.columns) : row[column]
              row = row[c.columns]
              raw = allColumns ? row.raw : row.raw[column]
              $cell = allColumns ? row.$row.children() : row.$row.children().eq(column)
              if (hasCallback) {
                result = callback({
                  tbodyIndex: tbodyIndex,
                  rowIndex: rowIndex,
                  parsed: parsed,
                  raw: raw,
                  $row: row.$row,
                  $cell: $cell
                })
              }
              if (result !== false) {
                data.parsed.push(parsed)
                data.raw.push(raw)
                data.$cell.push($cell)
              }
            }
          }
          // return everything
          return data
        }
      }

      // get sorter, string, empty, etc options for each column from
      // jQuery data, metadata, header option or header class name ('sorter-false')
      // priority = jQuery data > meta > headers option > header class name
      ts.getData = function(h, ch, key) {
        var val = '',
          $h = $(h),
          m,
          cl
        if (!$h.length) {
          return ''
        }
        m = $.metadata ? $h.metadata() : false
        cl = ' ' + ($h.attr('class') || '')
        if (
          typeof $h.data(key) !== 'undefined' ||
          typeof $h.data(key.toLowerCase()) !== 'undefined'
        ) {
          // 'data-lockedOrder' is assigned to 'lockedorder'; but 'data-locked-order' is assigned to 'lockedOrder'
          // 'data-sort-initial-order' is assigned to 'sortInitialOrder'
          val += $h.data(key) || $h.data(key.toLowerCase())
        } else if (m && typeof m[key] !== 'undefined') {
          val += m[key]
        } else if (ch && typeof ch[key] !== 'undefined') {
          val += ch[key]
        } else if (cl !== ' ' && cl.match(' ' + key + '-')) {
          // include sorter class name 'sorter-text', etc; now works with 'sorter-my-custom-parser'
          val = cl.match(new RegExp('\\s' + key + '-([\\w-]+)'))[1] || ''
        }
        return $.trim(val)
      }

      ts.formatFloat = function(s, table) {
        s = s.replace(/(N.A.)|(--i)|(hmm...)/g, -1.0)
        if (typeof s !== 'string' || s === '') {
          return s
        }
        // allow using formatFloat without a table; defaults to US number format
        var i,
          t =
            table && table.config
              ? table.config.usNumberFormat !== false
              : typeof table !== 'undefined'
              ? table
              : true
        if (t) {
          // US Format - 1,234,567.89 -> 1234567.89
          s = s.replace(/,/g, '')
        } else {
          // German Format = 1.234.567,89 -> 1234567.89
          // French Format = 1 234 567,89 -> 1234567.89
          s = s.replace(/[\s|\.]/g, '').replace(/,/g, '.')
        }
        if (/^\s*\([.\d]+\)/.test(s)) {
          // make (#) into a negative number -> (10) = -10
          s = s.replace(/^\s*\(([.\d]+)\)/, '-$1')
        }
        i = parseFloat(s)
        // return the text instead of zero
        return isNaN(i) ? $.trim(s) : i
      }

      ts.isDigit = function(s) {
        // replace all unwanted chars and match
        return isNaN(s)
          ? /^[\-+(]?\d+[)]?$/.test(s.toString().replace(/[,.'"\s]|(N.A.)|(--i)|(hmm...)/g, ''))
          : s !== ''
      }
    }()
  })

  // make shortcut
  var ts = $.tablesorter

  // extend plugin scope
  $.fn.extend({
    tablesorter: ts.construct
  })

  // add default parsers
  ts.addParser({
    id: 'no-parser',
    is: function() {
      return false
    },
    format: function() {
      return ''
    },
    type: 'text'
  })

  ts.addParser({
    id: 'text',
    is: function() {
      return true
    },
    format: function(s, table) {
      var c = table.config
      if (s) {
        s = $.trim(c.ignoreCase ? s.toLocaleLowerCase() : s)
        s = c.sortLocaleCompare ? ts.replaceAccents(s) : s
      }
      return s
    },
    type: 'text'
  })

  ts.addParser({
    id: 'digit',
    is: function(s) {
      return ts.isDigit(s)
    },
    format: function(s, table) {
      var n = ts.formatFloat((s || '').replace(/([,<])/g, ''), table)
      return s && typeof n === 'number'
        ? n
        : s
        ? $.trim(s && table.config.ignoreCase ? s.toLocaleLowerCase() : s)
        : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'currency',
    is: function(s) {
      return /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/.test(
        (s || '').replace(/[+\-,.<>\s]/g, '')
      ) // £$€¤¥¢
    },
    format: function(s, table) {
      var n = ts.formatFloat((s || '').replace(/[^\w,.\s\-()]/g, ''), table)
      return s && typeof n === 'number'
        ? n
        : s
        ? $.trim(s && table.config.ignoreCase ? s.toLocaleLowerCase() : s)
        : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'rank',
    is: function(s) {
      return /[#?.]/.test(s)
    },
    format: function(s, table) {
      var n = ts.formatFloat((s || '').replace(/([#,])/g, ''), table)
      return s && typeof n === 'number'
        ? n
        : s
        ? $.trim(s && table.config.ignoreCase ? s.toLocaleLowerCase() : s)
        : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'url',
    is: function(s) {
      return /^(https?|ftp|file):\/\//.test(s)
    },
    format: function(s) {
      return s ? $.trim(s.replace(/(https?|ftp|file):\/\//, '')) : s
    },
    parsed: true, // filter widget flag
    type: 'text'
  })

  ts.addParser({
    id: 'isoDate',
    is: function(s) {
      return /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/.test(s)
    },
    format: function(s, table) {
      var date = s ? new Date(s.replace(/-/g, '/')) : s
      return date instanceof Date && isFinite(date) ? date.getTime() : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'percent',
    is: function(s) {
      return /(\d\s*?%|%\s*?\d)/.test(s) && s.length < 15
    },
    format: function(s, table) {
      return s ? ts.formatFloat(s.replace(/%/g, ''), table) : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'usLongDate',
    is: function(s) {
      // two digit years are not allowed cross-browser
      // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
      return (
        /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i.test(s) ||
        /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i.test(s)
      )
    },
    format: function(s, table) {
      var date = s ? new Date(s.replace(/(\S)([AP]M)$/i, '$1 $2')) : s
      return date instanceof Date && isFinite(date) ? date.getTime() : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd'
    is: function(s) {
      // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
      return /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/.test(
        (s || '').replace(/\s+/g, ' ').replace(/[\-.,]/g, '/')
      )
    },
    format: function(s, table, cell, cellIndex) {
      if (s) {
        var date,
          d,
          c = table.config,
          ci = c.$headerIndexed[cellIndex],
          format =
            (ci.length && ci[0].dateFormat) ||
            ts.getData(ci, ts.getColumnData(table, c.headers, cellIndex), 'dateFormat') ||
            c.dateFormat
        d = s.replace(/\s+/g, ' ').replace(/[\-.,]/g, '/') // escaped - because JSHint in Firefox was showing it as an error
        if (format === 'mmddyyyy') {
          d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, '$3/$1/$2')
        } else if (format === 'ddmmyyyy') {
          d = d.replace(/(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/, '$3/$2/$1')
        } else if (format === 'yyyymmdd') {
          d = d.replace(/(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/, '$1/$2/$3')
        }
        date = new Date(d)
        return date instanceof Date && isFinite(date) ? date.getTime() : s
      }
      return s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'time',
    is: function(s) {
      return /^(([0-2]?\d:[0-5]\d)|([0-1]?\d:[0-5]\d\s?([AP]M)))$/i.test(s)
    },
    format: function(s, table) {
      var date = s ? new Date('2000/01/01 ' + s.replace(/(\S)([AP]M)$/i, '$1 $2')) : s
      return date instanceof Date && isFinite(date) ? date.getTime() : s
    },
    type: 'numeric'
  })

  ts.addParser({
    id: 'metadata',
    is: function() {
      return false
    },
    format: function(s, table, cell) {
      var c = table.config,
        p = !c.parserMetadataName ? 'sortValue' : c.parserMetadataName
      return $(cell).metadata()[p]
    },
    type: 'numeric'
  })

  // add default widgets
  ts.addWidget({
    id: 'zebra',
    priority: 90,
    format: function(table, c, wo) {
      var $tv,
        $tr,
        row,
        even,
        time,
        k,
        i,
        len,
        child = new RegExp(c.cssChildRow, 'i'),
        b = c.$tbodies.add(
          $(c.namespace + '_extra_table').children('tbody:not(.' + c.cssInfoBlock + ')')
        )
      if (c.debug) {
        time = new Date()
      }
      for (k = 0; k < b.length; k++) {
        // loop through the visible rows
        row = 0
        $tv = b
          .eq(k)
          .children('tr:visible')
          .not(c.selectorRemove)
        len = $tv.length
        for (i = 0; i < len; i++) {
          $tr = $tv.eq(i)
          // style child rows the same way the parent row was styled
          if (!child.test($tr[0].className)) {
            row++
          }
          even = row % 2 === 0
          $tr.removeClass(wo.zebra[even ? 1 : 0]).addClass(wo.zebra[even ? 0 : 1])
        }
      }
    },
    remove: function(table, c, wo, refreshing) {
      if (refreshing) {
        return
      }
      var k,
        $tb,
        b = c.$tbodies,
        rmv = (wo.zebra || ['even', 'odd']).join(' ')
      for (k = 0; k < b.length; k++) {
        $tb = ts.processTbody(table, b.eq(k), true) // remove tbody
        $tb.children().removeClass(rmv)
        ts.processTbody(table, $tb, false) // restore tbody
      }
    }
  })
})(jQuery)

/*! @sentry/browser 5.12.1 (16de493d) | https://github.com/getsentry/sentry-javascript */
var Sentry=function(n){var t=function(n,r){return(t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,t){n.__proto__=t}||function(n,t){for(var r in t)t.hasOwnProperty(r)&&(n[r]=t[r])})(n,r)};function r(n,r){function e(){this.constructor=n}t(n,r),n.prototype=null===r?Object.create(r):(e.prototype=r.prototype,new e)}var e=function(){return(e=Object.assign||function(n){for(var t,r=1,e=arguments.length;r<e;r++)for(var i in t=arguments[r])Object.prototype.hasOwnProperty.call(t,i)&&(n[i]=t[i]);return n}).apply(this,arguments)};function i(n){var t="function"==typeof Symbol&&n[Symbol.iterator],r=0;return t?t.call(n):{next:function(){return n&&r>=n.length&&(n=void 0),{value:n&&n[r++],done:!n}}}}function o(n,t){var r="function"==typeof Symbol&&n[Symbol.iterator];if(!r)return n;var e,i,o=r.call(n),u=[];try{for(;(void 0===t||t-- >0)&&!(e=o.next()).done;)u.push(e.value)}catch(n){i={error:n}}finally{try{e&&!e.done&&(r=o.return)&&r.call(o)}finally{if(i)throw i.error}}return u}function u(){for(var n=[],t=0;t<arguments.length;t++)n=n.concat(o(arguments[t]));return n}var c,a,s,f;!function(n){n[n.None=0]="None",n[n.Error=1]="Error",n[n.Debug=2]="Debug",n[n.Verbose=3]="Verbose"}(c||(c={})),(a=n.Severity||(n.Severity={})).Fatal="fatal",a.Error="error",a.Warning="warning",a.Log="log",a.Info="info",a.Debug="debug",a.Critical="critical",function(n){n.fromString=function(t){switch(t){case"debug":return n.Debug;case"info":return n.Info;case"warn":case"warning":return n.Warning;case"error":return n.Error;case"fatal":return n.Fatal;case"critical":return n.Critical;case"log":default:return n.Log}}}(n.Severity||(n.Severity={})),function(n){n.Ok="ok",n.DealineExceeded="deadline_exceeded",n.Unauthenticated="unauthenticated",n.PermissionDenied="permission_denied",n.NotFound="not_found",n.ResourceExhausted="resource_exhausted",n.InvalidArgument="invalid_argument",n.Unimplemented="unimplemented",n.Unavailable="unavailable",n.InternalError="internal_error",n.UnknownError="unknown_error",n.Cancelled="cancelled",n.AlreadyExists="already_exists",n.FailedPrecondition="failed_precondition",n.Aborted="aborted",n.OutOfRange="out_of_range",n.DataLoss="data_loss"}(s||(s={})),function(n){n.fromHttpCode=function(t){if(t<400)return n.Ok;if(t>=400&&t<500)switch(t){case 401:return n.Unauthenticated;case 403:return n.PermissionDenied;case 404:return n.NotFound;case 409:return n.AlreadyExists;case 413:return n.FailedPrecondition;case 429:return n.ResourceExhausted;default:return n.InvalidArgument}if(t>=500&&t<600)switch(t){case 501:return n.Unimplemented;case 503:return n.Unavailable;case 504:return n.DealineExceeded;default:return n.InternalError}return n.UnknownError}}(s||(s={})),(f=n.Status||(n.Status={})).Unknown="unknown",f.Skipped="skipped",f.Success="success",f.RateLimit="rate_limit",f.Invalid="invalid",f.Failed="failed",function(n){n.fromHttpCode=function(t){return t>=200&&t<300?n.Success:429===t?n.RateLimit:t>=400&&t<500?n.Invalid:t>=500?n.Failed:n.Unknown}}(n.Status||(n.Status={}));var h=Object.setPrototypeOf||({__proto__:[]}instanceof Array?function(n,t){return n.__proto__=t,n}:function(n,t){for(var r in t)n.hasOwnProperty(r)||(n[r]=t[r]);return n});var v=function(n){function t(t){var r=this.constructor,e=n.call(this,t)||this;return e.message=t,e.name=r.prototype.constructor.name,h(e,r.prototype),e}return r(t,n),t}(Error);function l(n){switch(Object.prototype.toString.call(n)){case"[object Error]":case"[object Exception]":case"[object DOMException]":return!0;default:return x(n,Error)}}function d(n){return"[object ErrorEvent]"===Object.prototype.toString.call(n)}function p(n){return"[object DOMError]"===Object.prototype.toString.call(n)}function y(n){return"[object String]"===Object.prototype.toString.call(n)}function m(n){return null===n||"object"!=typeof n&&"function"!=typeof n}function b(n){return"[object Object]"===Object.prototype.toString.call(n)}function w(n){return"undefined"!=typeof Event&&x(n,Event)}function g(n){return"undefined"!=typeof Element&&x(n,Element)}function E(n){return Boolean(n&&n.then&&"function"==typeof n.then)}function x(n,t){try{return n instanceof t}catch(n){return!1}}function j(n,t){return void 0===t&&(t=0),"string"!=typeof n||0===t?n:n.length<=t?n:n.substr(0,t)+"..."}function k(n,t){if(!Array.isArray(n))return"";for(var r=[],e=0;e<n.length;e++){var i=n[e];try{r.push(String(i))}catch(n){r.push("[value cannot be serialized]")}}return r.join(t)}function S(n,t){return r=t,"[object RegExp]"===Object.prototype.toString.call(r)?t.test(n):"string"==typeof t&&-1!==n.indexOf(t);var r}function _(){return"[object process]"===Object.prototype.toString.call("undefined"!=typeof process?process:0)}var O={};function T(){return _()?global:"undefined"!=typeof window?window:"undefined"!=typeof self?self:O}function D(){var n=T(),t=n.crypto||n.msCrypto;if(void 0!==t&&t.getRandomValues){var r=new Uint16Array(8);t.getRandomValues(r),r[3]=4095&r[3]|16384,r[4]=16383&r[4]|32768;var e=function(n){for(var t=n.toString(16);t.length<4;)t="0"+t;return t};return e(r[0])+e(r[1])+e(r[2])+e(r[3])+e(r[4])+e(r[5])+e(r[6])+e(r[7])}return"xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g,function(n){var t=16*Math.random()|0;return("x"===n?t:3&t|8).toString(16)})}function R(n){if(!n)return{};var t=n.match(/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/);if(!t)return{};var r=t[6]||"",e=t[8]||"";return{host:t[4],path:t[5],protocol:t[2],relative:t[5]+r+e}}function N(n){if(n.message)return n.message;if(n.exception&&n.exception.values&&n.exception.values[0]){var t=n.exception.values[0];return t.type&&t.value?t.type+": "+t.value:t.type||t.value||n.event_id||"<unknown>"}return n.event_id||"<unknown>"}function I(n){var t=T();if(!("console"in t))return n();var r=t.console,e={};["debug","info","warn","error","log","assert"].forEach(function(n){n in t.console&&r[n].__sentry_original__&&(e[n]=r[n],r[n]=r[n].__sentry_original__)});var i=n();return Object.keys(e).forEach(function(n){r[n]=e[n]}),i}function C(n,t,r){n.exception=n.exception||{},n.exception.values=n.exception.values||[],n.exception.values[0]=n.exception.values[0]||{},n.exception.values[0].value=n.exception.values[0].value||t||"",n.exception.values[0].type=n.exception.values[0].type||r||"Error"}function M(n,t){void 0===t&&(t={});try{n.exception.values[0].mechanism=n.exception.values[0].mechanism||{},Object.keys(t).forEach(function(r){n.exception.values[0].mechanism[r]=t[r]})}catch(n){}}function A(n){try{for(var t=n,r=[],e=0,i=0,o=" > ".length,u=void 0;t&&e++<5&&!("html"===(u=U(t))||e>1&&i+r.length*o+u.length>=80);)r.push(u),i+=u.length,t=t.parentNode;return r.reverse().join(" > ")}catch(n){return"<unknown>"}}function U(n){var t,r,e,i,o,u=n,c=[];if(!u||!u.tagName)return"";if(c.push(u.tagName.toLowerCase()),u.id&&c.push("#"+u.id),(t=u.className)&&y(t))for(r=t.split(/\s+/),o=0;o<r.length;o++)c.push("."+r[o]);var a=["type","name","title","alt"];for(o=0;o<a.length;o++)e=a[o],(i=u.getAttribute(e))&&c.push("["+e+'="'+i+'"]');return c.join("")}function q(){return(new Date).getTime()/1e3}var L=6e4;function H(n,t){if(!t)return L;var r=parseInt(""+t,10);if(!isNaN(r))return 1e3*r;var e=Date.parse(""+t);return isNaN(e)?L:e-n}var P="<anonymous>";function F(n){try{return n&&"function"==typeof n&&n.name||P}catch(n){return P}}var W=T(),X="Sentry Logger ",B=function(){function n(){this.t=!1}return n.prototype.disable=function(){this.t=!1},n.prototype.enable=function(){this.t=!0},n.prototype.log=function(){for(var n=[],t=0;t<arguments.length;t++)n[t]=arguments[t];this.t&&I(function(){W.console.log(X+"[Log]: "+n.join(" "))})},n.prototype.warn=function(){for(var n=[],t=0;t<arguments.length;t++)n[t]=arguments[t];this.t&&I(function(){W.console.warn(X+"[Warn]: "+n.join(" "))})},n.prototype.error=function(){for(var n=[],t=0;t<arguments.length;t++)n[t]=arguments[t];this.t&&I(function(){W.console.error(X+"[Error]: "+n.join(" "))})},n}();W.__SENTRY__=W.__SENTRY__||{};var $,G=W.__SENTRY__.logger||(W.__SENTRY__.logger=new B),J=function(){function n(){this.i="function"==typeof WeakSet,this.o=this.i?new WeakSet:[]}return n.prototype.memoize=function(n){if(this.i)return!!this.o.has(n)||(this.o.add(n),!1);for(var t=0;t<this.o.length;t++){if(this.o[t]===n)return!0}return this.o.push(n),!1},n.prototype.unmemoize=function(n){if(this.i)this.o.delete(n);else for(var t=0;t<this.o.length;t++)if(this.o[t]===n){this.o.splice(t,1);break}},n}();function z(n,t,r){if(t in n){var e=n[t],i=r(e);if("function"==typeof i)try{i.prototype=i.prototype||{},Object.defineProperties(i,{__sentry_original__:{enumerable:!1,value:e}})}catch(n){}n[t]=i}}function V(n){if(l(n)){var t=n,r={message:t.message,name:t.name,stack:t.stack};for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&(r[e]=t[e]);return r}if(w(n)){var i=n,o={};o.type=i.type;try{o.target=g(i.target)?A(i.target):Object.prototype.toString.call(i.target)}catch(n){o.target="<unknown>"}try{o.currentTarget=g(i.currentTarget)?A(i.currentTarget):Object.prototype.toString.call(i.currentTarget)}catch(n){o.currentTarget="<unknown>"}for(var e in"undefined"!=typeof CustomEvent&&x(n,CustomEvent)&&(o.detail=i.detail),i)Object.prototype.hasOwnProperty.call(i,e)&&(o[e]=i);return o}return n}function K(n){return function(n){return~-encodeURI(n).split(/%..|./).length}(JSON.stringify(n))}function Q(n,t,r){void 0===t&&(t=3),void 0===r&&(r=102400);var e=nn(n,t);return K(e)>r?Q(n,t-1,r):e}function Y(n,t){return"domain"===t&&n&&"object"==typeof n&&n.u?"[Domain]":"domainEmitter"===t?"[DomainEmitter]":"undefined"!=typeof global&&n===global?"[Global]":"undefined"!=typeof window&&n===window?"[Window]":"undefined"!=typeof document&&n===document?"[Document]":b(r=n)&&"nativeEvent"in r&&"preventDefault"in r&&"stopPropagation"in r?"[SyntheticEvent]":"number"==typeof n&&n!=n?"[NaN]":void 0===n?"[undefined]":"function"==typeof n?"[Function: "+F(n)+"]":n;var r}function Z(n,t,r,e){if(void 0===r&&(r=1/0),void 0===e&&(e=new J),0===r)return function(n){var t=Object.prototype.toString.call(n);if("string"==typeof n)return n;if("[object Object]"===t)return"[Object]";if("[object Array]"===t)return"[Array]";var r=Y(n);return m(r)?r:t}(t);if(null!=t&&"function"==typeof t.toJSON)return t.toJSON();var i=Y(t,n);if(m(i))return i;var o=V(t),u=Array.isArray(t)?[]:{};if(e.memoize(t))return"[Circular ~]";for(var c in o)Object.prototype.hasOwnProperty.call(o,c)&&(u[c]=Z(c,o[c],r-1,e));return e.unmemoize(t),u}function nn(n,t){try{return JSON.parse(JSON.stringify(n,function(n,r){return Z(n,r,t)}))}catch(n){return"**non-serializable**"}}function tn(n,t){void 0===t&&(t=40);var r=Object.keys(V(n));if(r.sort(),!r.length)return"[object has no keys]";if(r[0].length>=t)return j(r[0],t);for(var e=r.length;e>0;e--){var i=r.slice(0,e).join(", ");if(!(i.length>t))return e===r.length?i:j(i,t)}return""}!function(n){n.PENDING="PENDING",n.RESOLVED="RESOLVED",n.REJECTED="REJECTED"}($||($={}));var rn=function(){function n(n){var t=this;this.s=$.PENDING,this.h=[],this.v=function(n){t.l($.RESOLVED,n)},this.p=function(n){t.l($.REJECTED,n)},this.l=function(n,r){t.s===$.PENDING&&(E(r)?r.then(t.v,t.p):(t.s=n,t.m=r,t.g()))},this.j=function(n){t.h=t.h.concat(n),t.g()},this.g=function(){t.s!==$.PENDING&&(t.s===$.REJECTED?t.h.forEach(function(n){n.onrejected&&n.onrejected(t.m)}):t.h.forEach(function(n){n.onfulfilled&&n.onfulfilled(t.m)}),t.h=[])};try{n(this.v,this.p)}catch(n){this.p(n)}}return n.prototype.toString=function(){return"[object SyncPromise]"},n.resolve=function(t){return new n(function(n){n(t)})},n.reject=function(t){return new n(function(n,r){r(t)})},n.all=function(t){return new n(function(r,e){if(Array.isArray(t))if(0!==t.length){var i=t.length,o=[];t.forEach(function(t,u){n.resolve(t).then(function(n){o[u]=n,0===(i-=1)&&r(o)}).then(null,e)})}else r([]);else e(new TypeError("Promise.all requires an array as input."))})},n.prototype.then=function(t,r){var e=this;return new n(function(n,i){e.j({onfulfilled:function(r){if(t)try{return void n(t(r))}catch(n){return void i(n)}else n(r)},onrejected:function(t){if(r)try{return void n(r(t))}catch(n){return void i(n)}else i(t)}})})},n.prototype.catch=function(n){return this.then(function(n){return n},n)},n.prototype.finally=function(t){var r=this;return new n(function(n,e){var i,o;return r.then(function(n){o=!1,i=n,t&&t()},function(n){o=!0,i=n,t&&t()}).then(function(){o?e(i):n(i)})})},n}(),en=function(){function n(n){this.k=n,this.S=[]}return n.prototype.isReady=function(){return void 0===this.k||this.length()<this.k},n.prototype.add=function(n){var t=this;return this.isReady()?(-1===this.S.indexOf(n)&&this.S.push(n),n.then(function(){return t.remove(n)}).then(null,function(){return t.remove(n).then(null,function(){})}),n):rn.reject(new v("Not adding Promise due to buffer limit reached."))},n.prototype.remove=function(n){return this.S.splice(this.S.indexOf(n),1)[0]},n.prototype.length=function(){return this.S.length},n.prototype.drain=function(n){var t=this;return new rn(function(r){var e=setTimeout(function(){n&&n>0&&r(!1)},n);rn.all(t.S).then(function(){clearTimeout(e),r(!0)}).then(null,function(){r(!0)})})},n}();function on(){if(!("fetch"in T()))return!1;try{return new Headers,new Request(""),new Response,!0}catch(n){return!1}}function un(n){return n&&/^function fetch\(\)\s+\{\s+\[native code\]\s+\}$/.test(n.toString())}function cn(){if(!on())return!1;try{return new Request("_",{referrerPolicy:"origin"}),!0}catch(n){return!1}}var an,sn=T(),fn={},hn={};function vn(n){if(!hn[n])switch(hn[n]=!0,n){case"console":!function(){if(!("console"in sn))return;["debug","info","warn","error","log","assert"].forEach(function(n){n in sn.console&&z(sn.console,n,function(t){return function(){for(var r=[],e=0;e<arguments.length;e++)r[e]=arguments[e];dn("console",{args:r,level:n}),t&&Function.prototype.apply.call(t,sn.console,r)}})})}();break;case"dom":!function(){if(!("document"in sn))return;sn.document.addEventListener("click",En("click",dn.bind(null,"dom")),!1),sn.document.addEventListener("keypress",xn(dn.bind(null,"dom")),!1),["EventTarget","Node"].forEach(function(n){var t=sn[n]&&sn[n].prototype;t&&t.hasOwnProperty&&t.hasOwnProperty("addEventListener")&&(z(t,"addEventListener",function(n){return function(t,r,e){return r&&r.handleEvent?("click"===t&&z(r,"handleEvent",function(n){return function(t){return En("click",dn.bind(null,"dom"))(t),n.call(this,t)}}),"keypress"===t&&z(r,"handleEvent",function(n){return function(t){return xn(dn.bind(null,"dom"))(t),n.call(this,t)}})):("click"===t&&En("click",dn.bind(null,"dom"),!0)(this),"keypress"===t&&xn(dn.bind(null,"dom"))(this)),n.call(this,t,r,e)}}),z(t,"removeEventListener",function(n){return function(t,r,e){var i=r;try{i=i&&(i.__sentry_wrapped__||i)}catch(n){}return n.call(this,t,i,e)}}))})}();break;case"xhr":!function(){if(!("XMLHttpRequest"in sn))return;var n=XMLHttpRequest.prototype;z(n,"open",function(n){return function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];var e=t[1];return this.__sentry_xhr__={method:y(t[0])?t[0].toUpperCase():t[0],url:t[1]},y(e)&&"POST"===this.__sentry_xhr__.method&&e.match(/sentry_key/)&&(this.__sentry_own_request__=!0),n.apply(this,t)}}),z(n,"send",function(n){return function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];var i=this,o={args:t,startTimestamp:Date.now(),xhr:i};function u(){if(4===i.readyState){try{i.__sentry_xhr__&&(i.__sentry_xhr__.status_code=i.status)}catch(n){}dn("xhr",e({},o,{endTimestamp:Date.now()}))}}return dn("xhr",e({},o)),"onreadystatechange"in i&&"function"==typeof i.onreadystatechange?z(i,"onreadystatechange",function(n){return function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];return u(),n.apply(i,t)}}):i.onreadystatechange=u,n.apply(this,t)}})}();break;case"fetch":!function(){if(!function(){if(!on())return!1;var n=T();if(un(n.fetch))return!0;var t=!1,r=n.document;if(r){var e=r.createElement("iframe");e.hidden=!0;try{r.head.appendChild(e),e.contentWindow&&e.contentWindow.fetch&&(t=un(e.contentWindow.fetch)),r.head.removeChild(e)}catch(n){G.warn("Could not create sandbox iframe for pure fetch check, bailing to window.fetch: ",n)}}return t}())return;z(sn,"fetch",function(n){return function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];var i={args:t,fetchData:{method:pn(t),url:yn(t)},startTimestamp:Date.now()};return dn("fetch",e({},i)),n.apply(sn,t).then(function(n){return dn("fetch",e({},i,{endTimestamp:Date.now(),response:n})),n},function(n){throw dn("fetch",e({},i,{endTimestamp:Date.now(),error:n})),n})}})}();break;case"history":!function(){if(n=T(),t=n.chrome,r=t&&t.app&&t.app.runtime,e="history"in n&&!!n.history.pushState&&!!n.history.replaceState,r||!e)return;var n,t,r,e;var i=sn.onpopstate;function o(n){return function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];var e=t.length>2?t[2]:void 0;if(e){var i=an,o=String(e);an=o,dn("history",{from:i,to:o})}return n.apply(this,t)}}sn.onpopstate=function(){for(var n=[],t=0;t<arguments.length;t++)n[t]=arguments[t];var r=sn.location.href,e=an;if(an=r,dn("history",{from:e,to:r}),i)return i.apply(this,n)},z(sn.history,"pushState",o),z(sn.history,"replaceState",o)}();break;default:G.warn("unknown instrumentation type:",n)}}function ln(n){n&&"string"==typeof n.type&&"function"==typeof n.callback&&(fn[n.type]=fn[n.type]||[],fn[n.type].push(n.callback),vn(n.type))}function dn(n,t){var r,e;if(n&&fn[n])try{for(var o=i(fn[n]||[]),u=o.next();!u.done;u=o.next()){var c=u.value;try{c(t)}catch(t){G.error("Error while triggering instrumentation handler.\nType: "+n+"\nName: "+F(c)+"\nError: "+t)}}}catch(n){r={error:n}}finally{try{u&&!u.done&&(e=o.return)&&e.call(o)}finally{if(r)throw r.error}}}function pn(n){return void 0===n&&(n=[]),"Request"in sn&&x(n[0],Request)&&n[0].method?String(n[0].method).toUpperCase():n[1]&&n[1].method?String(n[1].method).toUpperCase():"GET"}function yn(n){return void 0===n&&(n=[]),"string"==typeof n[0]?n[0]:"Request"in sn&&x(n[0],Request)?n[0].url:String(n[0])}var mn,bn,wn=1e3,gn=0;function En(n,t,r){return void 0===r&&(r=!1),function(e){mn=void 0,e&&bn!==e&&(bn=e,gn&&clearTimeout(gn),r?gn=setTimeout(function(){t({event:e,name:n})}):t({event:e,name:n}))}}function xn(n){return function(t){var r;try{r=t.target}catch(n){return}var e=r&&r.tagName;e&&("INPUT"===e||"TEXTAREA"===e||r.isContentEditable)&&(mn||En("input",n)(t),clearTimeout(mn),mn=setTimeout(function(){mn=void 0},wn))}}var jn=/^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w\.-]+)(?::(\d+))?\/(.+)/,kn=function(){function n(n){"string"==typeof n?this._(n):this.O(n),this.T()}return n.prototype.toString=function(n){void 0===n&&(n=!1);var t=this,r=t.host,e=t.path,i=t.pass,o=t.port,u=t.projectId;return t.protocol+"://"+t.user+(n&&i?":"+i:"")+"@"+r+(o?":"+o:"")+"/"+(e?e+"/":e)+u},n.prototype._=function(n){var t=jn.exec(n);if(!t)throw new v("Invalid Dsn");var r=o(t.slice(1),6),e=r[0],i=r[1],u=r[2],c=void 0===u?"":u,a=r[3],s=r[4],f=void 0===s?"":s,h="",l=r[5],d=l.split("/");d.length>1&&(h=d.slice(0,-1).join("/"),l=d.pop()),this.O({host:a,pass:c,path:h,projectId:l,port:f,protocol:e,user:i})},n.prototype.O=function(n){this.protocol=n.protocol,this.user=n.user,this.pass=n.pass||"",this.host=n.host,this.port=n.port||"",this.path=n.path||"",this.projectId=n.projectId},n.prototype.T=function(){var n=this;if(["protocol","user","host","projectId"].forEach(function(t){if(!n[t])throw new v("Invalid Dsn")}),"http"!==this.protocol&&"https"!==this.protocol)throw new v("Invalid Dsn");if(this.port&&isNaN(parseInt(this.port,10)))throw new v("Invalid Dsn")},n}(),Sn=function(){function n(){this.D=!1,this.R=[],this.N=[],this.I=[],this.C={},this.M={},this.A={},this.U={}}return n.prototype.addScopeListener=function(n){this.R.push(n)},n.prototype.addEventProcessor=function(n){return this.N.push(n),this},n.prototype.q=function(){var n=this;this.D||(this.D=!0,setTimeout(function(){n.R.forEach(function(t){t(n)}),n.D=!1}))},n.prototype.L=function(n,t,r,i){var o=this;return void 0===i&&(i=0),new rn(function(u,c){var a=n[i];if(null===t||"function"!=typeof a)u(t);else{var s=a(e({},t),r);E(s)?s.then(function(t){return o.L(n,t,r,i+1).then(u)}).then(null,c):o.L(n,s,r,i+1).then(u).then(null,c)}})},n.prototype.setUser=function(n){return this.C=n||{},this.q(),this},n.prototype.setTags=function(n){return this.M=e({},this.M,n),this.q(),this},n.prototype.setTag=function(n,t){var r;return this.M=e({},this.M,((r={})[n]=t,r)),this.q(),this},n.prototype.setExtras=function(n){return this.A=e({},this.A,n),this.q(),this},n.prototype.setExtra=function(n,t){var r;return this.A=e({},this.A,((r={})[n]=t,r)),this.q(),this},n.prototype.setFingerprint=function(n){return this.H=n,this.q(),this},n.prototype.setLevel=function(n){return this.P=n,this.q(),this},n.prototype.setTransaction=function(n){return this.F=n,this.W&&(this.W.transaction=n),this.q(),this},n.prototype.setContext=function(n,t){var r;return this.U=e({},this.U,((r={})[n]=t,r)),this.q(),this},n.prototype.setSpan=function(n){return this.W=n,this.q(),this},n.prototype.getSpan=function(){return this.W},n.clone=function(t){var r=new n;return t&&(r.I=u(t.I),r.M=e({},t.M),r.A=e({},t.A),r.U=e({},t.U),r.C=t.C,r.P=t.P,r.W=t.W,r.F=t.F,r.H=t.H,r.N=u(t.N)),r},n.prototype.clear=function(){return this.I=[],this.M={},this.A={},this.C={},this.U={},this.P=void 0,this.F=void 0,this.H=void 0,this.W=void 0,this.q(),this},n.prototype.addBreadcrumb=function(n,t){var r=e({timestamp:q()},n);return this.I=void 0!==t&&t>=0?u(this.I,[r]).slice(-t):u(this.I,[r]),this.q(),this},n.prototype.clearBreadcrumbs=function(){return this.I=[],this.q(),this},n.prototype.X=function(n){n.fingerprint=n.fingerprint?Array.isArray(n.fingerprint)?n.fingerprint:[n.fingerprint]:[],this.H&&(n.fingerprint=n.fingerprint.concat(this.H)),n.fingerprint&&!n.fingerprint.length&&delete n.fingerprint},n.prototype.applyToEvent=function(n,t){return this.A&&Object.keys(this.A).length&&(n.extra=e({},this.A,n.extra)),this.M&&Object.keys(this.M).length&&(n.tags=e({},this.M,n.tags)),this.C&&Object.keys(this.C).length&&(n.user=e({},this.C,n.user)),this.U&&Object.keys(this.U).length&&(n.contexts=e({},this.U,n.contexts)),this.P&&(n.level=this.P),this.F&&(n.transaction=this.F),this.X(n),n.breadcrumbs=u(n.breadcrumbs||[],this.I),n.breadcrumbs=n.breadcrumbs.length>0?n.breadcrumbs:void 0,this.L(u(_n(),this.N),n,t)},n}();function _n(){var n=T();return n.__SENTRY__=n.__SENTRY__||{},n.__SENTRY__.globalEventProcessors=n.__SENTRY__.globalEventProcessors||[],n.__SENTRY__.globalEventProcessors}function On(n){_n().push(n)}var Tn=3,Dn=function(){function n(n,t,r){void 0===t&&(t=new Sn),void 0===r&&(r=Tn),this.B=r,this.$=[],this.$.push({client:n,scope:t})}return n.prototype.G=function(n){for(var t,r=[],e=1;e<arguments.length;e++)r[e-1]=arguments[e];var i=this.getStackTop();i&&i.client&&i.client[n]&&(t=i.client)[n].apply(t,u(r,[i.scope]))},n.prototype.isOlderThan=function(n){return this.B<n},n.prototype.bindClient=function(n){this.getStackTop().client=n},n.prototype.pushScope=function(){var n=this.getStack(),t=n.length>0?n[n.length-1].scope:void 0,r=Sn.clone(t);return this.getStack().push({client:this.getClient(),scope:r}),r},n.prototype.popScope=function(){return void 0!==this.getStack().pop()},n.prototype.withScope=function(n){var t=this.pushScope();try{n(t)}finally{this.popScope()}},n.prototype.getClient=function(){return this.getStackTop().client},n.prototype.getScope=function(){return this.getStackTop().scope},n.prototype.getStack=function(){return this.$},n.prototype.getStackTop=function(){return this.$[this.$.length-1]},n.prototype.captureException=function(n,t){var r=this.J=D(),i=t;if(!t){var o=void 0;try{throw new Error("Sentry syntheticException")}catch(n){o=n}i={originalException:n,syntheticException:o}}return this.G("captureException",n,e({},i,{event_id:r})),r},n.prototype.captureMessage=function(n,t,r){var i=this.J=D(),o=r;if(!r){var u=void 0;try{throw new Error(n)}catch(n){u=n}o={originalException:n,syntheticException:u}}return this.G("captureMessage",n,t,e({},o,{event_id:i})),i},n.prototype.captureEvent=function(n,t){var r=this.J=D();return this.G("captureEvent",n,e({},t,{event_id:r})),r},n.prototype.lastEventId=function(){return this.J},n.prototype.addBreadcrumb=function(n,t){var r=this.getStackTop();if(r.scope&&r.client){var i=r.client.getOptions&&r.client.getOptions()||{},o=i.beforeBreadcrumb,u=void 0===o?null:o,c=i.maxBreadcrumbs,a=void 0===c?100:c;if(!(a<=0)){var s=q(),f=e({timestamp:s},n),h=u?I(function(){return u(f,t)}):f;null!==h&&r.scope.addBreadcrumb(h,Math.min(a,100))}}},n.prototype.setUser=function(n){var t=this.getStackTop();t.scope&&t.scope.setUser(n)},n.prototype.setTags=function(n){var t=this.getStackTop();t.scope&&t.scope.setTags(n)},n.prototype.setExtras=function(n){var t=this.getStackTop();t.scope&&t.scope.setExtras(n)},n.prototype.setTag=function(n,t){var r=this.getStackTop();r.scope&&r.scope.setTag(n,t)},n.prototype.setExtra=function(n,t){var r=this.getStackTop();r.scope&&r.scope.setExtra(n,t)},n.prototype.setContext=function(n,t){var r=this.getStackTop();r.scope&&r.scope.setContext(n,t)},n.prototype.configureScope=function(n){var t=this.getStackTop();t.scope&&t.client&&n(t.scope)},n.prototype.run=function(n){var t=Nn(this);try{n(this)}finally{Nn(t)}},n.prototype.getIntegration=function(n){var t=this.getClient();if(!t)return null;try{return t.getIntegration(n)}catch(t){return G.warn("Cannot retrieve integration "+n.id+" from the current Hub"),null}},n.prototype.startSpan=function(n,t){return void 0===t&&(t=!1),this.V("startSpan",n,t)},n.prototype.traceHeaders=function(){return this.V("traceHeaders")},n.prototype.V=function(n){for(var t=[],r=1;r<arguments.length;r++)t[r-1]=arguments[r];var e=Rn().__SENTRY__;if(e&&e.extensions&&"function"==typeof e.extensions[n])return e.extensions[n].apply(this,t);G.warn("Extension method "+n+" couldn't be found, doing nothing.")},n}();function Rn(){var n=T();return n.__SENTRY__=n.__SENTRY__||{extensions:{},hub:void 0},n}function Nn(n){var t=Rn(),r=Mn(t);return An(t,n),r}function In(){var n=Rn();return Cn(n)&&!Mn(n).isOlderThan(Tn)||An(n,new Dn),_()?function(n){try{var t=(i=module,o="domain",i.require(o)),r=t.active;if(!r)return Mn(n);if(!Cn(r)||Mn(r).isOlderThan(Tn)){var e=Mn(n).getStackTop();An(r,new Dn(e.client,Sn.clone(e.scope)))}return Mn(r)}catch(t){return Mn(n)}var i,o}(n):Mn(n)}function Cn(n){return!!(n&&n.__SENTRY__&&n.__SENTRY__.hub)}function Mn(n){return n&&n.__SENTRY__&&n.__SENTRY__.hub?n.__SENTRY__.hub:(n.__SENTRY__=n.__SENTRY__||{},n.__SENTRY__.hub=new Dn,n.__SENTRY__.hub)}function An(n,t){return!!n&&(n.__SENTRY__=n.__SENTRY__||{},n.__SENTRY__.hub=t,!0)}function Un(n){for(var t=[],r=1;r<arguments.length;r++)t[r-1]=arguments[r];var e=In();if(e&&e[n])return e[n].apply(e,u(t));throw new Error("No hub defined or "+n+" was not found on the hub, please open a bug report.")}function captureException(n){var t;try{throw new Error("Sentry syntheticException")}catch(n){t=n}return Un("captureException",n,{originalException:n,syntheticException:t})}function qn(n){Un("withScope",n)}var Ln=function(){function n(n){this.dsn=n,this.K=new kn(n)}return n.prototype.getDsn=function(){return this.K},n.prototype.getStoreEndpoint=function(){return""+this.Y()+this.getStoreEndpointPath()},n.prototype.getStoreEndpointWithUrlEncodedAuth=function(){var n,t={sentry_key:this.K.user,sentry_version:"7"};return this.getStoreEndpoint()+"?"+(n=t,Object.keys(n).map(function(t){return encodeURIComponent(t)+"="+encodeURIComponent(n[t])}).join("&"))},n.prototype.Y=function(){var n=this.K,t=n.protocol?n.protocol+":":"",r=n.port?":"+n.port:"";return t+"//"+n.host+r},n.prototype.getStoreEndpointPath=function(){var n=this.K;return(n.path?"/"+n.path:"")+"/api/"+n.projectId+"/store/"},n.prototype.getRequestHeaders=function(n,t){var r=this.K,e=["Sentry sentry_version=7"];return e.push("sentry_timestamp="+q()),e.push("sentry_client="+n+"/"+t),e.push("sentry_key="+r.user),r.pass&&e.push("sentry_secret="+r.pass),{"Content-Type":"application/json","X-Sentry-Auth":e.join(", ")}},n.prototype.getReportDialogEndpoint=function(n){void 0===n&&(n={});var t=this.K,r=this.Y()+(t.path?"/"+t.path:"")+"/api/embed/error-page/",e=[];for(var i in e.push("dsn="+t.toString()),n)if("user"===i){if(!n.user)continue;n.user.name&&e.push("name="+encodeURIComponent(n.user.name)),n.user.email&&e.push("email="+encodeURIComponent(n.user.email))}else e.push(encodeURIComponent(i)+"="+encodeURIComponent(n[i]));return e.length?r+"?"+e.join("&"):r},n}(),Hn=[];function Pn(n){var t={};return function(n){var t=n.defaultIntegrations&&u(n.defaultIntegrations)||[],r=n.integrations,e=[];if(Array.isArray(r)){var i=r.map(function(n){return n.name}),o=[];t.forEach(function(n){-1===i.indexOf(n.name)&&-1===o.indexOf(n.name)&&(e.push(n),o.push(n.name))}),r.forEach(function(n){-1===o.indexOf(n.name)&&(e.push(n),o.push(n.name))})}else"function"==typeof r?(e=r(t),e=Array.isArray(e)?e:[e]):e=u(t);var c=e.map(function(n){return n.name});return-1!==c.indexOf("Debug")&&e.push.apply(e,u(e.splice(c.indexOf("Debug"),1))),e}(n).forEach(function(n){t[n.name]=n,function(n){-1===Hn.indexOf(n.name)&&(n.setupOnce(On,In),Hn.push(n.name),G.log("Integration installed: "+n.name))}(n)}),t}var Fn,Wn=function(){function n(n,t){this.Z={},this.nn=!1,this.tn=new n(t),this.rn=t,t.dsn&&(this.en=new kn(t.dsn)),this.in()&&(this.Z=Pn(this.rn))}return n.prototype.captureException=function(n,t,r){var e=this,i=t&&t.event_id;return this.nn=!0,this.on().eventFromException(n,t).then(function(n){return e.un(n,t,r)}).then(function(n){i=n&&n.event_id,e.nn=!1}).then(null,function(n){G.error(n),e.nn=!1}),i},n.prototype.captureMessage=function(n,t,r,e){var i=this,o=r&&r.event_id;return this.nn=!0,(m(n)?this.on().eventFromMessage(""+n,t,r):this.on().eventFromException(n,r)).then(function(n){return i.un(n,r,e)}).then(function(n){o=n&&n.event_id,i.nn=!1}).then(null,function(n){G.error(n),i.nn=!1}),o},n.prototype.captureEvent=function(n,t,r){var e=this,i=t&&t.event_id;return this.nn=!0,this.un(n,t,r).then(function(n){i=n&&n.event_id,e.nn=!1}).then(null,function(n){G.error(n),e.nn=!1}),i},n.prototype.getDsn=function(){return this.en},n.prototype.getOptions=function(){return this.rn},n.prototype.flush=function(n){var t=this;return this.cn(n).then(function(r){return clearInterval(r.interval),t.on().getTransport().close(n).then(function(n){return r.ready&&n})})},n.prototype.close=function(n){var t=this;return this.flush(n).then(function(n){return t.getOptions().enabled=!1,n})},n.prototype.getIntegrations=function(){return this.Z||{}},n.prototype.getIntegration=function(n){try{return this.Z[n.id]||null}catch(t){return G.warn("Cannot retrieve integration "+n.id+" from the current Client"),null}},n.prototype.cn=function(n){var t=this;return new rn(function(r){var e=0,i=0;clearInterval(i),i=setInterval(function(){t.nn?(e+=1,n&&e>=n&&r({interval:i,ready:!1})):r({interval:i,ready:!0})},1)})},n.prototype.on=function(){return this.tn},n.prototype.in=function(){return!1!==this.getOptions().enabled&&void 0!==this.en},n.prototype.an=function(n,t,r){var i=this,o=this.getOptions(),u=o.environment,c=o.release,a=o.dist,s=o.maxValueLength,f=void 0===s?250:s,h=o.normalizeDepth,v=void 0===h?3:h,l=e({},n);void 0===l.environment&&void 0!==u&&(l.environment=u),void 0===l.release&&void 0!==c&&(l.release=c),void 0===l.dist&&void 0!==a&&(l.dist=a),l.message&&(l.message=j(l.message,f));var d=l.exception&&l.exception.values&&l.exception.values[0];d&&d.value&&(d.value=j(d.value,f));var p=l.request;p&&p.url&&(p.url=j(p.url,f)),void 0===l.event_id&&(l.event_id=r&&r.event_id?r.event_id:D()),this.sn(l.sdk);var y=rn.resolve(l);return t&&(y=t.applyToEvent(l,r)),y.then(function(n){return"number"==typeof v&&v>0?i.fn(n,v):n})},n.prototype.fn=function(n,t){return n?e({},n,n.breadcrumbs&&{breadcrumbs:n.breadcrumbs.map(function(n){return e({},n,n.data&&{data:nn(n.data,t)})})},n.user&&{user:nn(n.user,t)},n.contexts&&{contexts:nn(n.contexts,t)},n.extra&&{extra:nn(n.extra,t)}):null},n.prototype.sn=function(n){var t=Object.keys(this.Z);n&&t.length>0&&(n.integrations=t)},n.prototype.un=function(n,t,r){var e=this,i=this.getOptions(),o=i.beforeSend,u=i.sampleRate;return this.in()?"number"==typeof u&&Math.random()>u?rn.reject("This event has been sampled, will not send event."):new rn(function(i,u){e.an(n,r,t).then(function(n){if(null!==n){var r=n;try{if(t&&t.data&&!0===t.data.__sentry__||!o)return e.on().sendEvent(r),void i(r);var c=o(n,t);if(void 0===c)G.error("`beforeSend` method has to return `null` or a valid event.");else if(E(c))e.hn(c,i,u);else{if(null===(r=c))return G.log("`beforeSend` returned `null`, will not send event."),void i(null);e.on().sendEvent(r),i(r)}}catch(n){e.captureException(n,{data:{__sentry__:!0},originalException:n}),u("`beforeSend` threw an error, will not send event.")}}else u("An event processor returned null, will not send event.")}).then(null,function(){u("`beforeSend` threw an error, will not send event.")})}):rn.reject("SDK not enabled, will not send event.")},n.prototype.hn=function(n,t,r){var e=this;n.then(function(n){null!==n?(e.on().sendEvent(n),t(n)):r("`beforeSend` returned `null`, will not send event.")}).then(null,function(n){r("beforeSend rejected with "+n)})},n}(),Xn=function(){function t(){}return t.prototype.sendEvent=function(t){return rn.resolve({reason:"NoopTransport: Event has been skipped because no Dsn is configured.",status:n.Status.Skipped})},t.prototype.close=function(n){return rn.resolve(!0)},t}(),Bn=function(){function n(n){this.rn=n,this.rn.dsn||G.warn("No DSN provided, backend will not do anything."),this.vn=this.ln()}return n.prototype.ln=function(){return new Xn},n.prototype.eventFromException=function(n,t){throw new v("Backend has to implement `eventFromException` method")},n.prototype.eventFromMessage=function(n,t,r){throw new v("Backend has to implement `eventFromMessage` method")},n.prototype.sendEvent=function(n){this.vn.sendEvent(n).then(null,function(n){G.error("Error while sending event: "+n)})},n.prototype.getTransport=function(){return this.vn},n}();var $n=function(){function n(){this.name=n.id}return n.prototype.setupOnce=function(){Fn=Function.prototype.toString,Function.prototype.toString=function(){for(var n=[],t=0;t<arguments.length;t++)n[t]=arguments[t];var r=this.__sentry_original__||this;return Fn.apply(r,n)}},n.id="FunctionToString",n}(),Gn=[/^Script error\.?$/,/^Javascript error: Script error\.? on line 0$/],Jn=function(){function n(t){void 0===t&&(t={}),this.rn=t,this.name=n.id}return n.prototype.setupOnce=function(){On(function(t){var r=In();if(!r)return t;var e=r.getIntegration(n);if(e){var i=r.getClient(),o=i?i.getOptions():{},u=e.dn(o);if(e.pn(t,u))return null}return t})},n.prototype.pn=function(n,t){return this.yn(n,t)?(G.warn("Event dropped due to being internal Sentry Error.\nEvent: "+N(n)),!0):this.mn(n,t)?(G.warn("Event dropped due to being matched by `ignoreErrors` option.\nEvent: "+N(n)),!0):this.bn(n,t)?(G.warn("Event dropped due to being matched by `blacklistUrls` option.\nEvent: "+N(n)+".\nUrl: "+this.wn(n)),!0):!this.gn(n,t)&&(G.warn("Event dropped due to not being matched by `whitelistUrls` option.\nEvent: "+N(n)+".\nUrl: "+this.wn(n)),!0)},n.prototype.yn=function(n,t){if(void 0===t&&(t={}),!t.ignoreInternal)return!1;try{return n&&n.exception&&n.exception.values&&n.exception.values[0]&&"SentryError"===n.exception.values[0].type||!1}catch(n){return!1}},n.prototype.mn=function(n,t){return void 0===t&&(t={}),!(!t.ignoreErrors||!t.ignoreErrors.length)&&this.En(n).some(function(n){return t.ignoreErrors.some(function(t){return S(n,t)})})},n.prototype.bn=function(n,t){if(void 0===t&&(t={}),!t.blacklistUrls||!t.blacklistUrls.length)return!1;var r=this.wn(n);return!!r&&t.blacklistUrls.some(function(n){return S(r,n)})},n.prototype.gn=function(n,t){if(void 0===t&&(t={}),!t.whitelistUrls||!t.whitelistUrls.length)return!0;var r=this.wn(n);return!r||t.whitelistUrls.some(function(n){return S(r,n)})},n.prototype.dn=function(n){return void 0===n&&(n={}),{blacklistUrls:u(this.rn.blacklistUrls||[],n.blacklistUrls||[]),ignoreErrors:u(this.rn.ignoreErrors||[],n.ignoreErrors||[],Gn),ignoreInternal:void 0===this.rn.ignoreInternal||this.rn.ignoreInternal,whitelistUrls:u(this.rn.whitelistUrls||[],n.whitelistUrls||[])}},n.prototype.En=function(n){if(n.message)return[n.message];if(n.exception)try{var t=n.exception.values&&n.exception.values[0]||{},r=t.type,e=void 0===r?"":r,i=t.value,o=void 0===i?"":i;return[""+o,e+": "+o]}catch(t){return G.error("Cannot extract message for event "+N(n)),[]}return[]},n.prototype.wn=function(n){try{if(n.stacktrace){var t=n.stacktrace.frames;return t&&t[t.length-1].filename||null}if(n.exception){var r=n.exception.values&&n.exception.values[0].stacktrace&&n.exception.values[0].stacktrace.frames;return r&&r[r.length-1].filename||null}return null}catch(t){return G.error("Cannot extract url for event "+N(n)),null}},n.id="InboundFilters",n}(),zn=Object.freeze({FunctionToString:$n,InboundFilters:Jn}),Vn="?",Kn=/^\s*at (?:(.*?) ?\()?((?:file|https?|blob|chrome-extension|address|native|eval|webpack|<anonymous>|[-a-z]+:|.*bundle|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,Qn=/^\s*(.*?)(?:\((.*?)\))?(?:^|@)?((?:file|https?|blob|chrome|webpack|resource|moz-extension).*?:\/.*?|\[native code\]|[^@]*(?:bundle|\d+\.js))(?::(\d+))?(?::(\d+))?\s*$/i,Yn=/^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,Zn=/(\S+) line (\d+)(?: > eval line \d+)* > eval/i,nt=/\((\S*)(?::(\d+))(?::(\d+))\)/;function tt(n){var t=null,r=n&&n.framesToPop;try{if(t=function(n){if(!n||!n.stacktrace)return null;for(var t,r=n.stacktrace,e=/ line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,i=/ line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i,o=r.split("\n"),u=[],c=0;c<o.length;c+=2){var a=null;(t=e.exec(o[c]))?a={url:t[2],func:t[3],args:[],line:+t[1],column:null}:(t=i.exec(o[c]))&&(a={url:t[6],func:t[3]||t[4],args:t[5]?t[5].split(","):[],line:+t[1],column:+t[2]}),a&&(!a.func&&a.line&&(a.func=Vn),u.push(a))}if(!u.length)return null;return{message:et(n),name:n.name,stack:u}}(n))return rt(t,r)}catch(n){}try{if(t=function(n){if(!n||!n.stack)return null;for(var t,r,e,i=[],o=n.stack.split("\n"),u=0;u<o.length;++u){if(r=Kn.exec(o[u])){var c=r[2]&&0===r[2].indexOf("native");r[2]&&0===r[2].indexOf("eval")&&(t=nt.exec(r[2]))&&(r[2]=t[1],r[3]=t[2],r[4]=t[3]),e={url:r[2]&&0===r[2].indexOf("address at ")?r[2].substr("address at ".length):r[2],func:r[1]||Vn,args:c?[r[2]]:[],line:r[3]?+r[3]:null,column:r[4]?+r[4]:null}}else if(r=Yn.exec(o[u]))e={url:r[2],func:r[1]||Vn,args:[],line:+r[3],column:r[4]?+r[4]:null};else{if(!(r=Qn.exec(o[u])))continue;r[3]&&r[3].indexOf(" > eval")>-1&&(t=Zn.exec(r[3]))?(r[1]=r[1]||"eval",r[3]=t[1],r[4]=t[2],r[5]=""):0!==u||r[5]||void 0===n.columnNumber||(i[0].column=n.columnNumber+1),e={url:r[3],func:r[1]||Vn,args:r[2]?r[2].split(","):[],line:r[4]?+r[4]:null,column:r[5]?+r[5]:null}}!e.func&&e.line&&(e.func=Vn),i.push(e)}if(!i.length)return null;return{message:et(n),name:n.name,stack:i}}(n))return rt(t,r)}catch(n){}return{message:et(n),name:n&&n.name,stack:[],failed:!0}}function rt(n,t){try{return e({},n,{stack:n.stack.slice(t)})}catch(t){return n}}function et(n){var t=n&&n.message;return t?t.error&&"string"==typeof t.error.message?t.error.message:t:"No error message"}var it=50;function ot(n){var t=ct(n.stack),r={type:n.name,value:n.message};return t&&t.length&&(r.stacktrace={frames:t}),void 0===r.type&&""===r.value&&(r.value="Unrecoverable error caught"),r}function ut(n){return{exception:{values:[ot(n)]}}}function ct(n){if(!n||!n.length)return[];var t=n,r=t[0].func||"",e=t[t.length-1].func||"";return-1===r.indexOf("captureMessage")&&-1===r.indexOf("captureException")||(t=t.slice(1)),-1!==e.indexOf("sentryWrapped")&&(t=t.slice(0,-1)),t.map(function(n){return{colno:null===n.column?void 0:n.column,filename:n.url||t[0].url,function:n.func||"?",in_app:!0,lineno:null===n.line?void 0:n.line}}).slice(0,it).reverse()}function at(n,t,r){var e,i;if(void 0===r&&(r={}),d(n)&&n.error)return e=ut(tt(n=n.error));if(p(n)||(i=n,"[object DOMException]"===Object.prototype.toString.call(i))){var o=n,u=o.name||(p(o)?"DOMError":"DOMException"),c=o.message?u+": "+o.message:u;return C(e=st(c,t,r),c),e}return l(n)?e=ut(tt(n)):b(n)||w(n)?(M(e=function(n,t,r){var e={exception:{values:[{type:w(n)?n.constructor.name:r?"UnhandledRejection":"Error",value:"Non-Error "+(r?"promise rejection":"exception")+" captured with keys: "+tn(n)}]},extra:{__serialized__:Q(n)}};if(t){var i=ct(tt(t).stack);e.stacktrace={frames:i}}return e}(n,t,r.rejection),{synthetic:!0}),e):(C(e=st(n,t,r),""+n,void 0),M(e,{synthetic:!0}),e)}function st(n,t,r){void 0===r&&(r={});var e={message:n};if(r.attachStacktrace&&t){var i=ct(tt(t).stack);e.stacktrace={frames:i}}return e}var ft=function(){function n(n){this.options=n,this.S=new en(30),this.url=new Ln(this.options.dsn).getStoreEndpointWithUrlEncodedAuth()}return n.prototype.sendEvent=function(n){throw new v("Transport Class has to implement `sendEvent` method")},n.prototype.close=function(n){return this.S.drain(n)},n}(),ht=T(),vt=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.xn=new Date(Date.now()),n}return r(e,t),e.prototype.sendEvent=function(t){var r=this;if(new Date(Date.now())<this.xn)return Promise.reject({event:t,reason:"Transport locked till "+this.xn+" due to too many requests.",status:429});var e={body:JSON.stringify(t),method:"POST",referrerPolicy:cn()?"origin":""};return void 0!==this.options.headers&&(e.headers=this.options.headers),this.S.add(new rn(function(t,i){ht.fetch(r.url,e).then(function(e){var o=n.Status.fromHttpCode(e.status);if(o!==n.Status.Success){if(o===n.Status.RateLimit){var u=Date.now();r.xn=new Date(u+H(u,e.headers.get("Retry-After"))),G.warn("Too many requests, backing off till: "+r.xn)}i(e)}else t({status:o})}).catch(i)}))},e}(ft),lt=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.xn=new Date(Date.now()),n}return r(e,t),e.prototype.sendEvent=function(t){var r=this;return new Date(Date.now())<this.xn?Promise.reject({event:t,reason:"Transport locked till "+this.xn+" due to too many requests.",status:429}):this.S.add(new rn(function(e,i){var o=new XMLHttpRequest;for(var u in o.onreadystatechange=function(){if(4===o.readyState){var t=n.Status.fromHttpCode(o.status);if(t!==n.Status.Success){if(t===n.Status.RateLimit){var u=Date.now();r.xn=new Date(u+H(u,o.getResponseHeader("Retry-After"))),G.warn("Too many requests, backing off till: "+r.xn)}i(o)}else e({status:t})}},o.open("POST",r.url),r.options.headers)r.options.headers.hasOwnProperty(u)&&o.setRequestHeader(u,r.options.headers[u]);o.send(JSON.stringify(t))}))},e}(ft),dt=Object.freeze({BaseTransport:ft,FetchTransport:vt,XHRTransport:lt}),pt=function(t){function i(){return null!==t&&t.apply(this,arguments)||this}return r(i,t),i.prototype.ln=function(){if(!this.rn.dsn)return t.prototype.ln.call(this);var n=e({},this.rn.transportOptions,{dsn:this.rn.dsn});return this.rn.transport?new this.rn.transport(n):on()?new vt(n):new lt(n)},i.prototype.eventFromException=function(t,r){var e=at(t,r&&r.syntheticException||void 0,{attachStacktrace:this.rn.attachStacktrace});return M(e,{handled:!0,type:"generic"}),e.level=n.Severity.Error,r&&r.event_id&&(e.event_id=r.event_id),rn.resolve(e)},i.prototype.eventFromMessage=function(t,r,e){void 0===r&&(r=n.Severity.Info);var i=st(t,e&&e.syntheticException||void 0,{attachStacktrace:this.rn.attachStacktrace});return i.level=r,e&&e.event_id&&(i.event_id=e.event_id),rn.resolve(i)},i}(Bn),yt="sentry.javascript.browser",mt=function(n){function t(t){return void 0===t&&(t={}),n.call(this,pt,t)||this}return r(t,n),t.prototype.an=function(t,r,i){return t.platform=t.platform||"javascript",t.sdk=e({},t.sdk,{name:yt,packages:u(t.sdk&&t.sdk.packages||[],[{name:"npm:@sentry/browser",version:"5.12.1"}]),version:"5.12.1"}),n.prototype.an.call(this,t,r,i)},t.prototype.showReportDialog=function(n){void 0===n&&(n={});var t=T().document;if(t)if(this.in()){var r=n.dsn||this.getDsn();if(n.eventId)if(r){var e=t.createElement("script");e.async=!0,e.src=new Ln(r).getReportDialogEndpoint(n),n.onLoad&&(e.onload=n.onLoad),(t.head||t.body).appendChild(e)}else G.error("Missing `Dsn` option in showReportDialog call");else G.error("Missing `eventId` option in showReportDialog call")}else G.error("Trying to call showReportDialog with Sentry Client is disabled")},t}(Wn),bt=0;function wt(){return bt>0}function gt(n,t,r){if(void 0===t&&(t={}),"function"!=typeof n)return n;try{if(n.__sentry__)return n;if(n.__sentry_wrapped__)return n.__sentry_wrapped__}catch(t){return n}var sentryWrapped=function(){var i=Array.prototype.slice.call(arguments);try{r&&"function"==typeof r&&r.apply(this,arguments);var o=i.map(function(n){return gt(n,t)});return n.handleEvent?n.handleEvent.apply(this,o):n.apply(this,o)}catch(n){throw bt+=1,setTimeout(function(){bt-=1}),qn(function(r){r.addEventProcessor(function(n){var r=e({},n);return t.mechanism&&(C(r,void 0,void 0),M(r,t.mechanism)),r.extra=e({},r.extra,{arguments:i}),r}),captureException(n)}),n}};try{for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(sentryWrapped[i]=n[i])}catch(n){}n.prototype=n.prototype||{},sentryWrapped.prototype=n.prototype,Object.defineProperty(n,"__sentry_wrapped__",{enumerable:!1,value:sentryWrapped}),Object.defineProperties(sentryWrapped,{__sentry__:{enumerable:!1,value:!0},__sentry_original__:{enumerable:!1,value:n}});try{Object.getOwnPropertyDescriptor(sentryWrapped,"name").configurable&&Object.defineProperty(sentryWrapped,"name",{get:function(){return n.name}})}catch(n){}return sentryWrapped}var Et=function(){function t(n){this.name=t.id,this.jn=T(),this.kn=null,this.Sn=null,this._n=!1,this.On=!1,this.rn=e({onerror:!0,onunhandledrejection:!0},n)}return t.prototype.setupOnce=function(){Error.stackTraceLimit=50,this.rn.onerror&&(G.log("Global Handler attached: onerror"),this.Tn()),this.rn.onunhandledrejection&&(G.log("Global Handler attached: onunhandledrejection"),this.Dn())},t.prototype.Tn=function(){if(!this._n){var n=this;this.kn=this.jn.onerror,this.jn.onerror=function(r,e,i,o,u){var c=In(),a=c.getIntegration(t),s=u&&!0===u.__sentry_own_request__;if(!a||wt()||s)return!!n.kn&&n.kn.apply(this,arguments);var f=c.getClient(),h=m(u)?n.Rn(r,e,i,o):n.Nn(at(u,void 0,{attachStacktrace:f&&f.getOptions().attachStacktrace,rejection:!1}),e,i,o);return M(h,{handled:!1,type:"onerror"}),c.captureEvent(h,{originalException:u}),!!n.kn&&n.kn.apply(this,arguments)},this._n=!0}},t.prototype.Dn=function(){if(!this.On){var r=this;this.Sn=this.jn.onunhandledrejection,this.jn.onunhandledrejection=function(e){var i=e;try{i=e&&"reason"in e?e.reason:e}catch(n){}var o=In(),u=o.getIntegration(t),c=i&&!0===i.__sentry_own_request__;if(!u||wt()||c)return!r.Sn||r.Sn.apply(this,arguments);var a=o.getClient(),s=m(i)?r.In(i):at(i,void 0,{attachStacktrace:a&&a.getOptions().attachStacktrace,rejection:!0});return s.level=n.Severity.Error,M(s,{handled:!1,type:"onunhandledrejection"}),o.captureEvent(s,{originalException:i}),!r.Sn||r.Sn.apply(this,arguments)},this.On=!0}},t.prototype.Rn=function(n,t,r,e){var i,o=d(n)?n.message:n;if(y(o)){var u=o.match(/^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i);u&&(i=u[1],o=u[2])}var c={exception:{values:[{type:i||"Error",value:o}]}};return this.Nn(c,t,r,e)},t.prototype.In=function(n){return{exception:{values:[{type:"UnhandledRejection",value:"Non-Error promise rejection captured with value: "+n}]}}},t.prototype.Nn=function(n,t,r,e){n.exception=n.exception||{},n.exception.values=n.exception.values||[],n.exception.values[0]=n.exception.values[0]||{},n.exception.values[0].stacktrace=n.exception.values[0].stacktrace||{},n.exception.values[0].stacktrace.frames=n.exception.values[0].stacktrace.frames||[];var i=isNaN(parseInt(e,10))?void 0:e,o=isNaN(parseInt(r,10))?void 0:r,u=y(t)&&t.length>0?t:function(){try{return document.location.href}catch(n){return""}}();return 0===n.exception.values[0].stacktrace.frames.length&&n.exception.values[0].stacktrace.frames.push({colno:i,filename:u,function:"?",in_app:!0,lineno:o}),n},t.id="GlobalHandlers",t}(),xt=function(){function n(){this.Cn=0,this.name=n.id}return n.prototype.Mn=function(n){return function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];var e=t[0];return t[0]=gt(e,{mechanism:{data:{function:F(n)},handled:!0,type:"instrument"}}),n.apply(this,t)}},n.prototype.An=function(n){return function(t){return n(gt(t,{mechanism:{data:{function:"requestAnimationFrame",handler:F(n)},handled:!0,type:"instrument"}}))}},n.prototype.Un=function(n){var t=T(),r=t[n]&&t[n].prototype;r&&r.hasOwnProperty&&r.hasOwnProperty("addEventListener")&&(z(r,"addEventListener",function(t){return function(r,e,i){try{"function"==typeof e.handleEvent&&(e.handleEvent=gt(e.handleEvent.bind(e),{mechanism:{data:{function:"handleEvent",handler:F(e),target:n},handled:!0,type:"instrument"}}))}catch(n){}return t.call(this,r,gt(e,{mechanism:{data:{function:"addEventListener",handler:F(e),target:n},handled:!0,type:"instrument"}}),i)}}),z(r,"removeEventListener",function(n){return function(t,r,e){var i=r;try{i=i&&(i.__sentry_wrapped__||i)}catch(n){}return n.call(this,t,i,e)}}))},n.prototype.qn=function(n){return function(){for(var t=this,r=[],e=0;e<arguments.length;e++)r[e]=arguments[e];return["onload","onerror","onprogress"].forEach(function(n){n in t&&"function"==typeof t[n]&&z(t,n,function(t){return gt(t,{mechanism:{data:{function:n,handler:F(t)},handled:!0,type:"instrument"}})})}),"onreadystatechange"in this&&"function"==typeof this.onreadystatechange&&z(this,"onreadystatechange",function(n){var t={mechanism:{data:{function:"onreadystatechange",handler:F(n)},handled:!0,type:"instrument"}};return n.__sentry_original__&&(t.mechanism.data.handler=F(n.__sentry_original__)),gt(n,t)}),n.apply(this,r)}},n.prototype.setupOnce=function(){this.Cn=this.Cn;var n=T();z(n,"setTimeout",this.Mn.bind(this)),z(n,"setInterval",this.Mn.bind(this)),z(n,"requestAnimationFrame",this.An.bind(this)),"XMLHttpRequest"in n&&z(XMLHttpRequest.prototype,"send",this.qn.bind(this)),["EventTarget","Window","Node","ApplicationCache","AudioTrackList","ChannelMergerNode","CryptoOperation","EventSource","FileReader","HTMLUnknownElement","IDBDatabase","IDBRequest","IDBTransaction","KeyOperation","MediaController","MessagePort","ModalWindow","Notification","SVGElementInstance","Screen","TextTrack","TextTrackCue","TextTrackList","WebSocket","WebSocketWorker","Worker","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload"].forEach(this.Un.bind(this))},n.id="TryCatch",n}(),jt=function(){function t(n){this.name=t.id,this.rn=e({console:!0,dom:!0,fetch:!0,history:!0,sentry:!0,xhr:!0},n)}return t.prototype.Ln=function(t){var r={category:"console",data:{arguments:t.args,logger:"console"},level:n.Severity.fromString(t.level),message:k(t.args," ")};if("assert"===t.level){if(!1!==t.args[0])return;r.message="Assertion failed: "+(k(t.args.slice(1)," ")||"console.assert"),r.data.arguments=t.args.slice(1)}In().addBreadcrumb(r,{input:t.args,level:t.level})},t.prototype.Hn=function(n){var t;try{t=n.event.target?A(n.event.target):A(n.event)}catch(n){t="<unknown>"}0!==t.length&&In().addBreadcrumb({category:"ui."+n.name,message:t},{event:event,name:n.name})},t.prototype.Pn=function(n){if(n.endTimestamp){if(n.xhr.__sentry_own_request__)return;In().addBreadcrumb({category:"xhr",data:n.xhr.__sentry_xhr__,type:"http"},{xhr:n.xhr})}else n.xhr.__sentry_own_request__&&kt(n.args[0])},t.prototype.Fn=function(t){if(t.endTimestamp){var r=In().getClient(),i=r&&r.getDsn();if(i){var o=new Ln(i).getStoreEndpoint();if(o&&-1!==t.fetchData.url.indexOf(o)&&"POST"===t.fetchData.method&&t.args[1]&&t.args[1].body)return void kt(t.args[1].body)}t.error?In().addBreadcrumb({category:"fetch",data:e({},t.fetchData,{status_code:t.response.status}),level:n.Severity.Error,type:"http"},{data:t.error,input:t.args}):In().addBreadcrumb({category:"fetch",data:e({},t.fetchData,{status_code:t.response.status}),type:"http"},{input:t.args,response:t.response})}},t.prototype.Wn=function(n){var t=T(),r=n.from,e=n.to,i=R(t.location.href),o=R(r),u=R(e);o.path||(o=i),i.protocol===u.protocol&&i.host===u.host&&(e=u.relative),i.protocol===o.protocol&&i.host===o.host&&(r=o.relative),In().addBreadcrumb({category:"navigation",data:{from:r,to:e}})},t.prototype.setupOnce=function(){var n=this;this.rn.console&&ln({callback:function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];n.Ln.apply(n,u(t))},type:"console"}),this.rn.dom&&ln({callback:function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];n.Hn.apply(n,u(t))},type:"dom"}),this.rn.xhr&&ln({callback:function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];n.Pn.apply(n,u(t))},type:"xhr"}),this.rn.fetch&&ln({callback:function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];n.Fn.apply(n,u(t))},type:"fetch"}),this.rn.history&&ln({callback:function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];n.Wn.apply(n,u(t))},type:"history"})},t.id="Breadcrumbs",t}();function kt(t){try{var r=JSON.parse(t);In().addBreadcrumb({category:"sentry",event_id:r.event_id,level:r.level||n.Severity.fromString("error"),message:N(r)},{event:r})}catch(n){G.error("Error while adding sentry type breadcrumb")}}var St="cause",_t=5,Ot=function(){function n(t){void 0===t&&(t={}),this.name=n.id,this.Xn=t.key||St,this.k=t.limit||_t}return n.prototype.setupOnce=function(){On(function(t,r){var e=In().getIntegration(n);return e?e.Bn(t,r):t})},n.prototype.Bn=function(n,t){if(!(n.exception&&n.exception.values&&t&&x(t.originalException,Error)))return n;var r=this.$n(t.originalException,this.Xn);return n.exception.values=u(r,n.exception.values),n},n.prototype.$n=function(n,t,r){if(void 0===r&&(r=[]),!x(n[t],Error)||r.length+1>=this.k)return r;var e=ot(tt(n[t]));return this.$n(n[t],t,u([e],r))},n.id="LinkedErrors",n}(),Tt=T(),Dt=function(){function n(){this.name=n.id}return n.prototype.setupOnce=function(){On(function(t){if(In().getIntegration(n)){if(!Tt.navigator||!Tt.location)return t;var r=t.request||{};return r.url=r.url||Tt.location.href,r.headers=r.headers||{},r.headers["User-Agent"]=Tt.navigator.userAgent,e({},t,{request:r})}return t})},n.id="UserAgent",n}(),Rt=Object.freeze({GlobalHandlers:Et,TryCatch:xt,Breadcrumbs:jt,LinkedErrors:Ot,UserAgent:Dt}),Nt=[new Jn,new $n,new xt,new jt,new Et,new Ot,new Dt];var It={},Ct=T();Ct.Sentry&&Ct.Sentry.Integrations&&(It=Ct.Sentry.Integrations);var Mt=e({},It,zn,Rt);return n.BrowserClient=mt,n.Hub=Dn,n.Integrations=Mt,n.SDK_NAME=yt,n.SDK_VERSION="5.12.1",n.Scope=Sn,n.Transports=dt,n.addBreadcrumb=function(n){Un("addBreadcrumb",n)},n.addGlobalEventProcessor=On,n.captureEvent=function(n){return Un("captureEvent",n)},n.captureException=captureException,n.captureMessage=function(n,t){var r;try{throw new Error(n)}catch(n){r=n}return Un("captureMessage",n,t,{originalException:n,syntheticException:r})},n.close=function(n){var t=In().getClient();return t?t.close(n):rn.reject(!1)},n.configureScope=function(n){Un("configureScope",n)},n.defaultIntegrations=Nt,n.flush=function(n){var t=In().getClient();return t?t.flush(n):rn.reject(!1)},n.forceLoad=function(){},n.getCurrentHub=In,n.getHubFromCarrier=Mn,n.init=function(n){if(void 0===n&&(n={}),void 0===n.defaultIntegrations&&(n.defaultIntegrations=Nt),void 0===n.release){var t=T();t.SENTRY_RELEASE&&t.SENTRY_RELEASE.id&&(n.release=t.SENTRY_RELEASE.id)}!function(n,t){!0===t.debug&&G.enable(),In().bindClient(new n(t))}(mt,n)},n.lastEventId=function(){return In().lastEventId()},n.onLoad=function(n){n()},n.setContext=function(n,t){Un("setContext",n,t)},n.setExtra=function(n,t){Un("setExtra",n,t)},n.setExtras=function(n){Un("setExtras",n)},n.setTag=function(n,t){Un("setTag",n,t)},n.setTags=function(n){Un("setTags",n)},n.setUser=function(n){Un("setUser",n)},n.showReportDialog=function(n){void 0===n&&(n={}),n.eventId||(n.eventId=In().lastEventId());var t=In().getClient();t&&t.showReportDialog(n)},n.withScope=qn,n.wrap=function(n){return gt(n)()},n}({});
//# sourceMappingURL=bundle.min.js.map
/*
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * watermark.js - Create watermarked images with Canvas and JS
 *
 * Version: 1 (2011-04-04)
 * Copyright (c) 2011	Patrick Wied ( http://www.patrick-wied.at )
 * This code is licensed under the terms of the MIT LICENSE
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Updated by: Greg Mercer
 */

//If the file has injected many times
if ($('.jsContainer').length >= 1) {
  throw new Error('Injected!')
}

;(function(w) {
  var wm = (function(w) {
    var doc = w.document,
      gcanvas = {},
      gctx = {},
      imgQueue = [],
      className = 'js-watermark',
      watermark = false,
      watermarkPosition = 'bottom-right',
      watermarkPath = 'watermark.png?' + +new Date(),
      opacity = 255 / (100 / 50), // 50%
      initCanvas = function() {
        gcanvas = doc.createElement('canvas')
        gcanvas.style.cssText = 'display:none;'
        gctx = gcanvas.getContext('2d')
        doc.body.appendChild(gcanvas)
      },
      initWatermark = function() {
        watermark = new Image()
        watermark.crossOrigin = 'Anonymous'
        watermark.src = ''
        watermark.src = watermarkPath

        if (opacity != 255) {
          if (!watermark.complete)
            watermark.onload = function() {
              applyTransparency()
            }
          else applyTransparency()
        }
      },
      // function for applying transparency to the watermark
      applyTransparency = function() {
        var w = watermark.width || watermark.offsetWidth,
          h = watermark.height || watermark.offsetHeight
        setCanvasSize(w, h)
        gctx.drawImage(watermark, 0, 0)
        var image = gctx.getImageData(0, 0, w, h)
        var imageData = image.data,
          length = imageData.length
        for (var i = 3; i < length; i += 4) {
          imageData[i] = imageData[i] < opacity ? imageData[i] : opacity
        }
        image.data = imageData
        gctx.putImageData(image, 0, 0)
        watermark.onload = null
        watermark.src = ''
        watermark.src = gcanvas.toDataURL()
        // assign img attributes to the transparent watermark
        // because browsers recalculation doesn't work as fast as needed
        watermark.width = w
        watermark.height = h
      },
      configure = function(config) {
        if (config['watermark']) watermark = config['watermark']
        if (config['path']) watermarkPath = config['path']
        if (config['position']) watermarkPosition = config['position']
        if (config['opacity']) opacity = 255 / (100 / config['opacity'])
        if (config['className']) className = config['className']

        initCanvas()
        initWatermark()
      }
    ;(setCanvasSize = function(w, h) {
      gcanvas.width = w
      gcanvas.height = h
    }),
      (applyWatermark = function(img, callback) {
        gcanvas.width = img.width || img.offsetWidth
        gcanvas.height = img.height || img.offsetHeight
        gctx.drawImage(img, 0, 0)
        var position = watermarkPosition,
          x = 0,
          y = 0

        if (position.indexOf('center') != -1) {
          x = (img.width - watermark.width) / 2
          y = (img.height - watermark.height) / 2
        } else if (position.indexOf('top') != -1 || position.indexOf('left') != -1) {
          if (position.indexOf('top') != -1) y = 10
          else y = gcanvas.height - watermark.height - 10

          if (position.indexOf('left') != -1) x = 10
          else x = gcanvas.width - watermark.width - 10
        }

        gctx.drawImage(watermark, x, y)
        img.src = gcanvas.toDataURL()

        //If I need to do something after the watermark has been added
        if (typeof callback != 'undefined') {
          callback.call(this)
        }
      })
    return {
      init: function(config) {
        configure(config)
      },
      applyWatermark: applyWatermark
    }
  })(w)
  w.wmark = wm
})(window)
